bosun 0.40.2 → 0.40.3
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/cli.mjs +86 -67
- package/infra/monitor.mjs +8 -3
- package/infra/update-check.mjs +1 -3
- package/package.json +1 -1
- package/server/ui-server.mjs +117 -0
- package/task/task-executor.mjs +690 -6
- package/task/task-store.mjs +116 -1
- package/ui/demo.html +26 -1
- package/ui/styles/components.css +43 -3
- package/ui/tabs/tasks.js +387 -97
- package/workflow/workflow-engine.mjs +30 -5
- package/workflow/workflow-nodes.mjs +102 -2
- package/workspace/workspace-manager.mjs +14 -0
- package/workspace/worktree-manager.mjs +2 -2
package/task/task-store.mjs
CHANGED
|
@@ -429,6 +429,7 @@ function defaultMeta() {
|
|
|
429
429
|
version: 1,
|
|
430
430
|
projectId: null,
|
|
431
431
|
lastFullSync: null,
|
|
432
|
+
epicDependencies: {},
|
|
432
433
|
sprintOrderMode: "parallel",
|
|
433
434
|
taskCount: 0,
|
|
434
435
|
stats: {
|
|
@@ -808,6 +809,25 @@ function ensureSprintsMap() {
|
|
|
808
809
|
return _store.sprints;
|
|
809
810
|
}
|
|
810
811
|
|
|
812
|
+
function ensureEpicDependenciesMap() {
|
|
813
|
+
if (!_store._meta || typeof _store._meta !== "object") {
|
|
814
|
+
_store._meta = defaultMeta();
|
|
815
|
+
}
|
|
816
|
+
if (!_store._meta.epicDependencies || typeof _store._meta.epicDependencies !== "object") {
|
|
817
|
+
_store._meta.epicDependencies = {};
|
|
818
|
+
}
|
|
819
|
+
const map = _store._meta.epicDependencies;
|
|
820
|
+
for (const [key, value] of Object.entries(map)) {
|
|
821
|
+
const epicId = String(key || "").trim();
|
|
822
|
+
if (!epicId) {
|
|
823
|
+
delete map[key];
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
826
|
+
map[epicId] = uniqueStringList(Array.isArray(value) ? value : []);
|
|
827
|
+
}
|
|
828
|
+
return map;
|
|
829
|
+
}
|
|
830
|
+
|
|
811
831
|
export function listSprints() {
|
|
812
832
|
ensureLoaded();
|
|
813
833
|
const sprints = Object.values(ensureSprintsMap());
|
|
@@ -1693,12 +1713,18 @@ export function getGlobalDagOfDags() {
|
|
|
1693
1713
|
return String(a.to).localeCompare(String(b.to));
|
|
1694
1714
|
});
|
|
1695
1715
|
|
|
1716
|
+
const epicDependencies = getEpicDependencies();
|
|
1717
|
+
|
|
1696
1718
|
return {
|
|
1697
1719
|
sprintOrderMode: mode,
|
|
1698
1720
|
sprintCount: nodes.length,
|
|
1699
1721
|
edgeCount: edges.length,
|
|
1722
|
+
epicDependencies,
|
|
1700
1723
|
nodes,
|
|
1701
|
-
edges
|
|
1724
|
+
edges: edges.map((edge) => ({
|
|
1725
|
+
...edge,
|
|
1726
|
+
kind: edge.taskLinks.some((link) => link?.type === 'sequence') ? 'sequential' : 'dependency',
|
|
1727
|
+
})),
|
|
1702
1728
|
};
|
|
1703
1729
|
}
|
|
1704
1730
|
|
|
@@ -1706,6 +1732,54 @@ export function getDagOfDags() {
|
|
|
1706
1732
|
return getGlobalDagOfDags();
|
|
1707
1733
|
}
|
|
1708
1734
|
|
|
1735
|
+
export function getEpicDependencies() {
|
|
1736
|
+
ensureLoaded();
|
|
1737
|
+
const map = ensureEpicDependenciesMap();
|
|
1738
|
+
return Object.entries(map).map(([epicId, dependencies]) => ({
|
|
1739
|
+
epicId,
|
|
1740
|
+
dependencies: uniqueStringList(Array.isArray(dependencies) ? dependencies : []),
|
|
1741
|
+
}));
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
export function setEpicDependencies(epicId, dependencies = []) {
|
|
1745
|
+
ensureLoaded();
|
|
1746
|
+
const normalizedEpicId = String(epicId || '').trim();
|
|
1747
|
+
if (!normalizedEpicId) return null;
|
|
1748
|
+
const cleaned = uniqueStringList((Array.isArray(dependencies) ? dependencies : [])
|
|
1749
|
+
.map((entry) => String(entry || '').trim())
|
|
1750
|
+
.filter((entry) => entry && entry !== normalizedEpicId));
|
|
1751
|
+
const map = ensureEpicDependenciesMap();
|
|
1752
|
+
if (cleaned.length > 0) map[normalizedEpicId] = cleaned;
|
|
1753
|
+
else delete map[normalizedEpicId];
|
|
1754
|
+
saveStore();
|
|
1755
|
+
return { epicId: normalizedEpicId, dependencies: [...(map[normalizedEpicId] || [])] };
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
export function addEpicDependency(epicId, dependencyEpicId) {
|
|
1759
|
+
ensureLoaded();
|
|
1760
|
+
const normalizedEpicId = String(epicId || '').trim();
|
|
1761
|
+
const normalizedDependency = String(dependencyEpicId || '').trim();
|
|
1762
|
+
if (!normalizedEpicId || !normalizedDependency || normalizedEpicId === normalizedDependency) return null;
|
|
1763
|
+
const map = ensureEpicDependenciesMap();
|
|
1764
|
+
const next = uniqueStringList([...(map[normalizedEpicId] || []), normalizedDependency]);
|
|
1765
|
+
map[normalizedEpicId] = next;
|
|
1766
|
+
saveStore();
|
|
1767
|
+
return { epicId: normalizedEpicId, dependencies: [...next] };
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
export function removeEpicDependency(epicId, dependencyEpicId) {
|
|
1771
|
+
ensureLoaded();
|
|
1772
|
+
const normalizedEpicId = String(epicId || '').trim();
|
|
1773
|
+
const normalizedDependency = String(dependencyEpicId || '').trim();
|
|
1774
|
+
if (!normalizedEpicId || !normalizedDependency) return null;
|
|
1775
|
+
const map = ensureEpicDependenciesMap();
|
|
1776
|
+
const next = uniqueStringList((map[normalizedEpicId] || []).filter((entry) => entry !== normalizedDependency));
|
|
1777
|
+
if (next.length > 0) map[normalizedEpicId] = next;
|
|
1778
|
+
else delete map[normalizedEpicId];
|
|
1779
|
+
saveStore();
|
|
1780
|
+
return { epicId: normalizedEpicId, dependencies: [...(map[normalizedEpicId] || [])] };
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1709
1783
|
export function canStartTask(taskId, options = {}) {
|
|
1710
1784
|
return canTaskStart(taskId, options);
|
|
1711
1785
|
}
|
|
@@ -1721,6 +1795,7 @@ export function canTaskStart(taskId, options = {}) {
|
|
|
1721
1795
|
blockingTaskIds: [],
|
|
1722
1796
|
missingDependencyTaskIds: [],
|
|
1723
1797
|
blockingSprintIds: [],
|
|
1798
|
+
blockingEpicIds: [],
|
|
1724
1799
|
sprintOrderMode,
|
|
1725
1800
|
};
|
|
1726
1801
|
}
|
|
@@ -1732,6 +1807,7 @@ export function canTaskStart(taskId, options = {}) {
|
|
|
1732
1807
|
blockingTaskIds: [],
|
|
1733
1808
|
missingDependencyTaskIds: [],
|
|
1734
1809
|
blockingSprintIds: [],
|
|
1810
|
+
blockingEpicIds: [],
|
|
1735
1811
|
sprintOrderMode,
|
|
1736
1812
|
};
|
|
1737
1813
|
}
|
|
@@ -1756,6 +1832,7 @@ export function canTaskStart(taskId, options = {}) {
|
|
|
1756
1832
|
blockingTaskIds: uniqueStringList(blockingTaskIds),
|
|
1757
1833
|
missingDependencyTaskIds: uniqueStringList(missingDependencyTaskIds),
|
|
1758
1834
|
blockingSprintIds: [],
|
|
1835
|
+
blockingEpicIds: [],
|
|
1759
1836
|
sprintOrderMode,
|
|
1760
1837
|
};
|
|
1761
1838
|
}
|
|
@@ -1787,6 +1864,42 @@ export function canTaskStart(taskId, options = {}) {
|
|
|
1787
1864
|
blockingTaskIds: uniqueStringList(blockingTaskIds),
|
|
1788
1865
|
missingDependencyTaskIds: [],
|
|
1789
1866
|
blockingSprintIds: [sprintId],
|
|
1867
|
+
blockingEpicIds: [],
|
|
1868
|
+
sprintOrderMode,
|
|
1869
|
+
sprintTaskOrderMode,
|
|
1870
|
+
};
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
const taskEpicId = String(task?.epicId || task?.meta?.epicId || '').trim();
|
|
1876
|
+
if (taskEpicId) {
|
|
1877
|
+
const epicDependenciesMap = ensureEpicDependenciesMap();
|
|
1878
|
+
const requiredEpics = uniqueStringList(epicDependenciesMap[taskEpicId] || []);
|
|
1879
|
+
if (requiredEpics.length > 0) {
|
|
1880
|
+
const blockingEpicIds = [];
|
|
1881
|
+
const blockingEpicTaskIds = [];
|
|
1882
|
+
for (const requiredEpicId of requiredEpics) {
|
|
1883
|
+
let hasIncomplete = false;
|
|
1884
|
+
for (const candidate of Object.values(_store.tasks)) {
|
|
1885
|
+
if (!candidate) continue;
|
|
1886
|
+
const candidateEpicId = String(candidate?.epicId || candidate?.meta?.epicId || '').trim();
|
|
1887
|
+
if (candidateEpicId !== requiredEpicId) continue;
|
|
1888
|
+
if (!isTaskTerminal(candidate)) {
|
|
1889
|
+
hasIncomplete = true;
|
|
1890
|
+
blockingEpicTaskIds.push(candidate.id);
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
if (hasIncomplete) blockingEpicIds.push(requiredEpicId);
|
|
1894
|
+
}
|
|
1895
|
+
if (blockingEpicIds.length > 0) {
|
|
1896
|
+
return {
|
|
1897
|
+
canStart: false,
|
|
1898
|
+
reason: "epic_dependencies_unresolved",
|
|
1899
|
+
blockingTaskIds: uniqueStringList(blockingEpicTaskIds),
|
|
1900
|
+
missingDependencyTaskIds: [],
|
|
1901
|
+
blockingSprintIds: [],
|
|
1902
|
+
blockingEpicIds: uniqueStringList(blockingEpicIds),
|
|
1790
1903
|
sprintOrderMode,
|
|
1791
1904
|
sprintTaskOrderMode,
|
|
1792
1905
|
};
|
|
@@ -1818,6 +1931,7 @@ export function canTaskStart(taskId, options = {}) {
|
|
|
1818
1931
|
blockingTaskIds: uniqueStringList(blockingTaskIds),
|
|
1819
1932
|
missingDependencyTaskIds: [],
|
|
1820
1933
|
blockingSprintIds: uniqueStringList(blockingSprintIds),
|
|
1934
|
+
blockingEpicIds: [],
|
|
1821
1935
|
sprintOrderMode,
|
|
1822
1936
|
sprintTaskOrderMode,
|
|
1823
1937
|
};
|
|
@@ -1831,6 +1945,7 @@ export function canTaskStart(taskId, options = {}) {
|
|
|
1831
1945
|
blockingTaskIds: [],
|
|
1832
1946
|
missingDependencyTaskIds: [],
|
|
1833
1947
|
blockingSprintIds: [],
|
|
1948
|
+
blockingEpicIds: [],
|
|
1834
1949
|
sprintOrderMode,
|
|
1835
1950
|
};
|
|
1836
1951
|
}
|
package/ui/demo.html
CHANGED
|
@@ -2964,6 +2964,30 @@
|
|
|
2964
2964
|
}
|
|
2965
2965
|
return { ok: true, data: { nodes, edges } };
|
|
2966
2966
|
}
|
|
2967
|
+
if (route === '/api/tasks/epic-dependencies') {
|
|
2968
|
+
const map = STATE.epicDependencies && typeof STATE.epicDependencies === 'object'
|
|
2969
|
+
? STATE.epicDependencies
|
|
2970
|
+
: (STATE.epicDependencies = {});
|
|
2971
|
+
if (method === 'GET') {
|
|
2972
|
+
const data = Object.entries(map).map(([epicId, dependencies]) => ({
|
|
2973
|
+
epicId,
|
|
2974
|
+
dependencies: Array.isArray(dependencies)
|
|
2975
|
+
? dependencies.map((value) => String(value || '').trim()).filter(Boolean)
|
|
2976
|
+
: [],
|
|
2977
|
+
}));
|
|
2978
|
+
return { ok: true, source: 'demo', data };
|
|
2979
|
+
}
|
|
2980
|
+
if (method === 'PUT') {
|
|
2981
|
+
const epicId = String(body?.epicId || body?.id || '').trim();
|
|
2982
|
+
if (!epicId) return { ok: false, error: 'epicId required' };
|
|
2983
|
+
const dependencies = Array.isArray(body?.dependencies)
|
|
2984
|
+
? body.dependencies.map((value) => String(value || '').trim()).filter(Boolean).filter((value) => value !== epicId)
|
|
2985
|
+
: [];
|
|
2986
|
+
if (dependencies.length) map[epicId] = [...new Set(dependencies)];
|
|
2987
|
+
else delete map[epicId];
|
|
2988
|
+
return { ok: true, source: 'demo', data: { epicId, dependencies: map[epicId] || [] } };
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2967
2991
|
if (route === '/api/tasks/attachments/upload') {
|
|
2968
2992
|
const taskId = body?.taskId || body?.id || params.get('taskId');
|
|
2969
2993
|
const t = findTask(taskId);
|
|
@@ -3117,7 +3141,8 @@
|
|
|
3117
3141
|
data: t,
|
|
3118
3142
|
dag: { sprintId: t.sprintId || null, sprint: { nodes: [] }, global: { nodes: [] } },
|
|
3119
3143
|
};
|
|
3120
|
-
}
|
|
3144
|
+
}
|
|
3145
|
+
if (route === '/api/tasks/retry') {
|
|
3121
3146
|
const t = findTask(body?.taskId);
|
|
3122
3147
|
if (t) { t.status = 'todo'; t.updated = Date.now(); addLog('info', 'task-executor', `Task retried: ${t.title}`); }
|
|
3123
3148
|
return { ok: true, data: t || null };
|
package/ui/styles/components.css
CHANGED
|
@@ -5461,14 +5461,49 @@ select.input {
|
|
|
5461
5461
|
radial-gradient(circle at 10% 20%, rgba(249, 115, 22, 0.12), transparent 40%),
|
|
5462
5462
|
radial-gradient(circle at 85% 80%, rgba(56, 189, 248, 0.08), transparent 40%),
|
|
5463
5463
|
var(--bg-card);
|
|
5464
|
-
overflow:
|
|
5465
|
-
padding:
|
|
5464
|
+
overflow: hidden;
|
|
5465
|
+
padding: 8px;
|
|
5466
|
+
min-height: 460px;
|
|
5467
|
+
touch-action: none;
|
|
5468
|
+
cursor: grab;
|
|
5469
|
+
}
|
|
5470
|
+
|
|
5471
|
+
.task-dag-canvas-wrap.is-panning {
|
|
5472
|
+
cursor: grabbing;
|
|
5466
5473
|
}
|
|
5467
5474
|
|
|
5468
5475
|
.task-dag-canvas {
|
|
5469
5476
|
width: 100%;
|
|
5470
5477
|
min-width: 680px;
|
|
5471
|
-
height:
|
|
5478
|
+
height: 460px;
|
|
5479
|
+
}
|
|
5480
|
+
|
|
5481
|
+
.task-dag-header-row {
|
|
5482
|
+
display: flex;
|
|
5483
|
+
flex-wrap: wrap;
|
|
5484
|
+
align-items: center;
|
|
5485
|
+
justify-content: space-between;
|
|
5486
|
+
gap: 8px;
|
|
5487
|
+
margin-bottom: 8px;
|
|
5488
|
+
}
|
|
5489
|
+
|
|
5490
|
+
.task-dag-controls {
|
|
5491
|
+
display: inline-flex;
|
|
5492
|
+
align-items: center;
|
|
5493
|
+
gap: 6px;
|
|
5494
|
+
flex-wrap: wrap;
|
|
5495
|
+
}
|
|
5496
|
+
|
|
5497
|
+
.task-dag-zoom-pill,
|
|
5498
|
+
.task-dag-wire-pill {
|
|
5499
|
+
display: inline-flex;
|
|
5500
|
+
align-items: center;
|
|
5501
|
+
border: 1px solid var(--border);
|
|
5502
|
+
border-radius: 999px;
|
|
5503
|
+
background: var(--bg-card-hover);
|
|
5504
|
+
padding: 3px 8px;
|
|
5505
|
+
font-size: 11px;
|
|
5506
|
+
color: var(--text-secondary);
|
|
5472
5507
|
}
|
|
5473
5508
|
|
|
5474
5509
|
.dag-node rect {
|
|
@@ -5480,6 +5515,11 @@ select.input {
|
|
|
5480
5515
|
filter: drop-shadow(0 6px 12px rgba(0, 0, 0, 0.35));
|
|
5481
5516
|
}
|
|
5482
5517
|
|
|
5518
|
+
.dag-node-selected rect {
|
|
5519
|
+
stroke: var(--accent);
|
|
5520
|
+
filter: drop-shadow(0 0 0 2px rgba(56, 189, 248, 0.25));
|
|
5521
|
+
}
|
|
5522
|
+
|
|
5483
5523
|
@media (max-width: 900px) {
|
|
5484
5524
|
.task-comment-composer {
|
|
5485
5525
|
grid-template-columns: 1fr;
|