mrvn-cli 0.4.5 → 0.4.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/index.js +276 -94
- package/dist/index.js.map +1 -1
- package/dist/marvin-serve.js +275 -93
- package/dist/marvin-serve.js.map +1 -1
- package/dist/marvin.js +276 -94
- package/dist/marvin.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -15723,6 +15723,23 @@ function getUpcomingData(store) {
|
|
|
15723
15723
|
}
|
|
15724
15724
|
|
|
15725
15725
|
// src/web/templates/layout.ts
|
|
15726
|
+
function collapsibleSection(sectionId, title, content, opts) {
|
|
15727
|
+
const tag = opts?.titleTag ?? "div";
|
|
15728
|
+
const cls = opts?.titleClass ?? "section-title";
|
|
15729
|
+
const collapsed = opts?.defaultCollapsed ? " collapsed" : "";
|
|
15730
|
+
return `
|
|
15731
|
+
<div class="collapsible${collapsed}" data-section-id="${escapeHtml(sectionId)}">
|
|
15732
|
+
<${tag} class="${cls} collapsible-header" onclick="toggleSection(this)">
|
|
15733
|
+
<svg class="collapsible-chevron" viewBox="0 0 16 16" width="14" height="14" fill="currentColor">
|
|
15734
|
+
<path d="M4.94 5.72a.75.75 0 0 1 1.06-.02L8 7.56l1.97-1.84a.75.75 0 1 1 1.02 1.1l-2.5 2.34a.75.75 0 0 1-1.02 0l-2.5-2.34a.75.75 0 0 1-.03-1.06z"/>
|
|
15735
|
+
</svg>
|
|
15736
|
+
<span>${title}</span>
|
|
15737
|
+
</${tag}>
|
|
15738
|
+
<div class="collapsible-body">
|
|
15739
|
+
${content}
|
|
15740
|
+
</div>
|
|
15741
|
+
</div>`;
|
|
15742
|
+
}
|
|
15726
15743
|
function escapeHtml(str) {
|
|
15727
15744
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
15728
15745
|
}
|
|
@@ -15890,6 +15907,32 @@ function layout(opts, body) {
|
|
|
15890
15907
|
${body}
|
|
15891
15908
|
</main>
|
|
15892
15909
|
</div>
|
|
15910
|
+
<script>
|
|
15911
|
+
function toggleSection(header) {
|
|
15912
|
+
var section = header.closest('.collapsible');
|
|
15913
|
+
if (!section) return;
|
|
15914
|
+
section.classList.toggle('collapsed');
|
|
15915
|
+
var id = section.getAttribute('data-section-id');
|
|
15916
|
+
if (id) {
|
|
15917
|
+
try {
|
|
15918
|
+
var state = JSON.parse(localStorage.getItem('marvin-collapsed') || '{}');
|
|
15919
|
+
state[id] = section.classList.contains('collapsed');
|
|
15920
|
+
localStorage.setItem('marvin-collapsed', JSON.stringify(state));
|
|
15921
|
+
} catch(e) {}
|
|
15922
|
+
}
|
|
15923
|
+
}
|
|
15924
|
+
// Restore collapsed state on load
|
|
15925
|
+
(function() {
|
|
15926
|
+
try {
|
|
15927
|
+
var state = JSON.parse(localStorage.getItem('marvin-collapsed') || '{}');
|
|
15928
|
+
document.querySelectorAll('.collapsible[data-section-id]').forEach(function(el) {
|
|
15929
|
+
var id = el.getAttribute('data-section-id');
|
|
15930
|
+
if (state[id] === true) el.classList.add('collapsed');
|
|
15931
|
+
else if (state[id] === false) el.classList.remove('collapsed');
|
|
15932
|
+
});
|
|
15933
|
+
} catch(e) {}
|
|
15934
|
+
})();
|
|
15935
|
+
</script>
|
|
15893
15936
|
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
|
|
15894
15937
|
<script>mermaid.initialize({
|
|
15895
15938
|
startOnLoad: true,
|
|
@@ -16713,13 +16756,60 @@ tr:hover td {
|
|
|
16713
16756
|
white-space: nowrap;
|
|
16714
16757
|
}
|
|
16715
16758
|
|
|
16759
|
+
.gantt-grid-line {
|
|
16760
|
+
position: absolute;
|
|
16761
|
+
top: 0;
|
|
16762
|
+
bottom: 0;
|
|
16763
|
+
width: 1px;
|
|
16764
|
+
background: var(--border);
|
|
16765
|
+
opacity: 0.35;
|
|
16766
|
+
}
|
|
16767
|
+
|
|
16768
|
+
.gantt-sprint-line {
|
|
16769
|
+
position: absolute;
|
|
16770
|
+
top: 0;
|
|
16771
|
+
bottom: 0;
|
|
16772
|
+
width: 1px;
|
|
16773
|
+
background: var(--text-dim);
|
|
16774
|
+
opacity: 0.3;
|
|
16775
|
+
}
|
|
16776
|
+
|
|
16716
16777
|
.gantt-today {
|
|
16717
16778
|
position: absolute;
|
|
16718
16779
|
top: 0;
|
|
16719
16780
|
bottom: 0;
|
|
16720
|
-
width:
|
|
16781
|
+
width: 3px;
|
|
16721
16782
|
background: var(--red);
|
|
16722
|
-
opacity: 0.
|
|
16783
|
+
opacity: 0.8;
|
|
16784
|
+
border-radius: 1px;
|
|
16785
|
+
}
|
|
16786
|
+
|
|
16787
|
+
/* Sprint band in timeline */
|
|
16788
|
+
.gantt-sprint-band-row {
|
|
16789
|
+
border-bottom: 1px solid var(--border);
|
|
16790
|
+
margin-bottom: 0.25rem;
|
|
16791
|
+
}
|
|
16792
|
+
|
|
16793
|
+
.gantt-sprint-band {
|
|
16794
|
+
height: 32px;
|
|
16795
|
+
}
|
|
16796
|
+
|
|
16797
|
+
.gantt-sprint-block {
|
|
16798
|
+
position: absolute;
|
|
16799
|
+
top: 2px;
|
|
16800
|
+
bottom: 2px;
|
|
16801
|
+
background: var(--bg-hover);
|
|
16802
|
+
border: 1px solid var(--border);
|
|
16803
|
+
border-radius: 4px;
|
|
16804
|
+
font-size: 0.65rem;
|
|
16805
|
+
color: var(--text-dim);
|
|
16806
|
+
display: flex;
|
|
16807
|
+
align-items: center;
|
|
16808
|
+
justify-content: center;
|
|
16809
|
+
overflow: hidden;
|
|
16810
|
+
white-space: nowrap;
|
|
16811
|
+
text-overflow: ellipsis;
|
|
16812
|
+
padding: 0 0.4rem;
|
|
16723
16813
|
}
|
|
16724
16814
|
|
|
16725
16815
|
/* Pie chart color overrides */
|
|
@@ -16781,6 +16871,40 @@ tr:hover td {
|
|
|
16781
16871
|
}
|
|
16782
16872
|
|
|
16783
16873
|
.text-dim { color: var(--text-dim); }
|
|
16874
|
+
|
|
16875
|
+
/* Collapsible sections */
|
|
16876
|
+
.collapsible-header {
|
|
16877
|
+
cursor: pointer;
|
|
16878
|
+
display: flex;
|
|
16879
|
+
align-items: center;
|
|
16880
|
+
gap: 0.4rem;
|
|
16881
|
+
user-select: none;
|
|
16882
|
+
}
|
|
16883
|
+
|
|
16884
|
+
.collapsible-header:hover {
|
|
16885
|
+
color: var(--accent);
|
|
16886
|
+
}
|
|
16887
|
+
|
|
16888
|
+
.collapsible-chevron {
|
|
16889
|
+
transition: transform 0.2s ease;
|
|
16890
|
+
flex-shrink: 0;
|
|
16891
|
+
}
|
|
16892
|
+
|
|
16893
|
+
.collapsible.collapsed .collapsible-chevron {
|
|
16894
|
+
transform: rotate(-90deg);
|
|
16895
|
+
}
|
|
16896
|
+
|
|
16897
|
+
.collapsible-body {
|
|
16898
|
+
overflow: hidden;
|
|
16899
|
+
max-height: 5000px;
|
|
16900
|
+
transition: max-height 0.3s ease, opacity 0.2s ease;
|
|
16901
|
+
opacity: 1;
|
|
16902
|
+
}
|
|
16903
|
+
|
|
16904
|
+
.collapsible.collapsed .collapsible-body {
|
|
16905
|
+
max-height: 0;
|
|
16906
|
+
opacity: 0;
|
|
16907
|
+
}
|
|
16784
16908
|
`;
|
|
16785
16909
|
}
|
|
16786
16910
|
|
|
@@ -16833,35 +16957,73 @@ function buildTimelineGantt(data, maxSprints = 6) {
|
|
|
16833
16957
|
);
|
|
16834
16958
|
tick += 7 * DAY;
|
|
16835
16959
|
}
|
|
16960
|
+
const gridLines = [];
|
|
16961
|
+
let gridTick = timelineStart;
|
|
16962
|
+
const gridStartDay = new Date(gridTick).getDay();
|
|
16963
|
+
gridTick += (8 - gridStartDay) % 7 * DAY;
|
|
16964
|
+
while (gridTick <= timelineEnd) {
|
|
16965
|
+
gridLines.push(`<div class="gantt-grid-line" style="left:${pct(gridTick).toFixed(2)}%"></div>`);
|
|
16966
|
+
gridTick += 7 * DAY;
|
|
16967
|
+
}
|
|
16968
|
+
const sprintBoundaries = /* @__PURE__ */ new Set();
|
|
16969
|
+
for (const sprint of visibleSprints) {
|
|
16970
|
+
sprintBoundaries.add(toMs(sprint.startDate));
|
|
16971
|
+
sprintBoundaries.add(toMs(sprint.endDate));
|
|
16972
|
+
}
|
|
16973
|
+
const sprintLines = [...sprintBoundaries].map(
|
|
16974
|
+
(ms) => `<div class="gantt-sprint-line" style="left:${pct(ms).toFixed(2)}%"></div>`
|
|
16975
|
+
);
|
|
16836
16976
|
const now = Date.now();
|
|
16837
16977
|
let todayMarker = "";
|
|
16838
16978
|
if (now >= timelineStart && now <= timelineEnd) {
|
|
16839
16979
|
todayMarker = `<div class="gantt-today" style="left:${pct(now).toFixed(2)}%"></div>`;
|
|
16840
16980
|
}
|
|
16841
|
-
const
|
|
16981
|
+
const sprintBlocks = visibleSprints.map((sprint) => {
|
|
16982
|
+
const sStart = toMs(sprint.startDate);
|
|
16983
|
+
const sEnd = toMs(sprint.endDate);
|
|
16984
|
+
const left = pct(sStart).toFixed(2);
|
|
16985
|
+
const width = (pct(sEnd) - pct(sStart)).toFixed(2);
|
|
16986
|
+
return `<div class="gantt-sprint-block" style="left:${left}%;width:${width}%">${sanitize(sprint.id, 20)}</div>`;
|
|
16987
|
+
}).join("");
|
|
16988
|
+
const sprintBandRow = `<div class="gantt-row gantt-sprint-band-row">
|
|
16989
|
+
<div class="gantt-label gantt-section-label">Sprints</div>
|
|
16990
|
+
<div class="gantt-track gantt-sprint-band">${sprintBlocks}</div>
|
|
16991
|
+
</div>`;
|
|
16992
|
+
const epicSpanMap = /* @__PURE__ */ new Map();
|
|
16842
16993
|
for (const sprint of visibleSprints) {
|
|
16843
16994
|
const sStart = toMs(sprint.startDate);
|
|
16844
16995
|
const sEnd = toMs(sprint.endDate);
|
|
16845
|
-
|
|
16846
|
-
|
|
16847
|
-
|
|
16848
|
-
|
|
16849
|
-
|
|
16850
|
-
|
|
16851
|
-
|
|
16852
|
-
|
|
16853
|
-
|
|
16854
|
-
|
|
16855
|
-
|
|
16856
|
-
|
|
16857
|
-
|
|
16858
|
-
|
|
16996
|
+
for (const eid of sprint.linkedEpics) {
|
|
16997
|
+
if (!epicMap.has(eid)) continue;
|
|
16998
|
+
const existing = epicSpanMap.get(eid);
|
|
16999
|
+
if (existing) {
|
|
17000
|
+
existing.startMs = Math.min(existing.startMs, sStart);
|
|
17001
|
+
existing.endMs = Math.max(existing.endMs, sEnd);
|
|
17002
|
+
} else {
|
|
17003
|
+
epicSpanMap.set(eid, { startMs: sStart, endMs: sEnd });
|
|
17004
|
+
}
|
|
17005
|
+
}
|
|
17006
|
+
}
|
|
17007
|
+
const sortedEpicIds = [...epicSpanMap.keys()].sort((a, b) => {
|
|
17008
|
+
const aSpan = epicSpanMap.get(a);
|
|
17009
|
+
const bSpan = epicSpanMap.get(b);
|
|
17010
|
+
if (aSpan.startMs !== bSpan.startMs) return aSpan.startMs - bSpan.startMs;
|
|
17011
|
+
return a.localeCompare(b);
|
|
17012
|
+
});
|
|
17013
|
+
const epicRows = sortedEpicIds.map((eid) => {
|
|
17014
|
+
const epic = epicMap.get(eid);
|
|
17015
|
+
const { startMs, endMs } = epicSpanMap.get(eid);
|
|
17016
|
+
const cls = epic.status === "done" || epic.status === "completed" ? "gantt-bar-done" : epic.status === "in-progress" || epic.status === "active" ? "gantt-bar-active" : epic.status === "blocked" ? "gantt-bar-blocked" : "gantt-bar-default";
|
|
17017
|
+
const left = pct(startMs).toFixed(2);
|
|
17018
|
+
const width = (pct(endMs) - pct(startMs)).toFixed(2);
|
|
17019
|
+
const label = sanitize(epic.id + " " + epic.title);
|
|
17020
|
+
return `<div class="gantt-row">
|
|
17021
|
+
<div class="gantt-label">${label}</div>
|
|
16859
17022
|
<div class="gantt-track">
|
|
16860
17023
|
<div class="gantt-bar ${cls}" style="left:${left}%;width:${width}%"></div>
|
|
16861
17024
|
</div>
|
|
16862
|
-
</div
|
|
16863
|
-
|
|
16864
|
-
}
|
|
17025
|
+
</div>`;
|
|
17026
|
+
}).join("\n");
|
|
16865
17027
|
const note = truncated ? `<div class="mermaid-note">${hiddenCount} earlier sprint${hiddenCount > 1 ? "s" : ""} not shown</div>` : "";
|
|
16866
17028
|
return `${note}
|
|
16867
17029
|
<div class="gantt">
|
|
@@ -16870,11 +17032,12 @@ function buildTimelineGantt(data, maxSprints = 6) {
|
|
|
16870
17032
|
<div class="gantt-label"></div>
|
|
16871
17033
|
<div class="gantt-track gantt-dates">${markers.join("")}</div>
|
|
16872
17034
|
</div>
|
|
16873
|
-
${
|
|
17035
|
+
${sprintBandRow}
|
|
17036
|
+
${epicRows}
|
|
16874
17037
|
</div>
|
|
16875
17038
|
<div class="gantt-overlay">
|
|
16876
17039
|
<div class="gantt-label"></div>
|
|
16877
|
-
<div class="gantt-track">${todayMarker}</div>
|
|
17040
|
+
<div class="gantt-track">${gridLines.join("")}${sprintLines.join("")}${todayMarker}</div>
|
|
16878
17041
|
</div>
|
|
16879
17042
|
</div>`;
|
|
16880
17043
|
}
|
|
@@ -17154,11 +17317,12 @@ function overviewPage(data, diagrams, navGroups) {
|
|
|
17154
17317
|
|
|
17155
17318
|
<div class="section-title"><a href="/timeline">Project Timeline →</a></div>
|
|
17156
17319
|
|
|
17157
|
-
|
|
17158
|
-
${buildArtifactFlowchart(diagrams)}
|
|
17320
|
+
${collapsibleSection("overview-relationships", "Artifact Relationships", buildArtifactFlowchart(diagrams))}
|
|
17159
17321
|
|
|
17160
|
-
|
|
17161
|
-
|
|
17322
|
+
${collapsibleSection(
|
|
17323
|
+
"overview-recent",
|
|
17324
|
+
"Recent Activity",
|
|
17325
|
+
data.recent.length > 0 ? `
|
|
17162
17326
|
<div class="table-wrap">
|
|
17163
17327
|
<table>
|
|
17164
17328
|
<thead>
|
|
@@ -17174,7 +17338,8 @@ function overviewPage(data, diagrams, navGroups) {
|
|
|
17174
17338
|
${rows}
|
|
17175
17339
|
</tbody>
|
|
17176
17340
|
</table>
|
|
17177
|
-
</div>` : `<div class="empty"><p>No documents yet.</p></div>`
|
|
17341
|
+
</div>` : `<div class="empty"><p>No documents yet.</p></div>`
|
|
17342
|
+
)}
|
|
17178
17343
|
`;
|
|
17179
17344
|
}
|
|
17180
17345
|
|
|
@@ -17319,23 +17484,24 @@ function garPage(report) {
|
|
|
17319
17484
|
<div class="label">Overall: ${escapeHtml(report.overall)}</div>
|
|
17320
17485
|
</div>
|
|
17321
17486
|
|
|
17322
|
-
|
|
17323
|
-
${areaCards}
|
|
17324
|
-
</div>
|
|
17487
|
+
${collapsibleSection("gar-areas", "Areas", `<div class="gar-areas">${areaCards}</div>`)}
|
|
17325
17488
|
|
|
17326
|
-
|
|
17327
|
-
|
|
17328
|
-
|
|
17329
|
-
|
|
17330
|
-
|
|
17331
|
-
|
|
17489
|
+
${collapsibleSection(
|
|
17490
|
+
"gar-status-dist",
|
|
17491
|
+
"Status Distribution",
|
|
17492
|
+
buildStatusPie("Action Status", {
|
|
17493
|
+
Open: report.metrics.scope.open,
|
|
17494
|
+
Done: report.metrics.scope.done,
|
|
17495
|
+
"In Progress": Math.max(0, report.metrics.scope.total - report.metrics.scope.open - report.metrics.scope.done)
|
|
17496
|
+
})
|
|
17497
|
+
)}
|
|
17332
17498
|
`;
|
|
17333
17499
|
}
|
|
17334
17500
|
|
|
17335
17501
|
// src/web/templates/pages/health.ts
|
|
17336
17502
|
function healthPage(report, metrics) {
|
|
17337
17503
|
const dotClass = `dot-${report.overall}`;
|
|
17338
|
-
function renderSection(title, categories) {
|
|
17504
|
+
function renderSection(sectionId, title, categories) {
|
|
17339
17505
|
const cards = categories.map(
|
|
17340
17506
|
(cat) => `
|
|
17341
17507
|
<div class="gar-area">
|
|
@@ -17347,10 +17513,9 @@ function healthPage(report, metrics) {
|
|
|
17347
17513
|
${cat.items.length > 0 ? `<ul>${cat.items.map((item) => `<li><span class="ref-id">${escapeHtml(item.id)}</span>${escapeHtml(item.detail)}</li>`).join("")}</ul>` : ""}
|
|
17348
17514
|
</div>`
|
|
17349
17515
|
).join("\n");
|
|
17350
|
-
return
|
|
17351
|
-
|
|
17352
|
-
|
|
17353
|
-
`;
|
|
17516
|
+
return collapsibleSection(sectionId, title, `<div class="gar-areas">${cards}</div>`, {
|
|
17517
|
+
titleClass: "health-section-title"
|
|
17518
|
+
});
|
|
17354
17519
|
}
|
|
17355
17520
|
return `
|
|
17356
17521
|
<div class="page-header">
|
|
@@ -17363,35 +17528,43 @@ function healthPage(report, metrics) {
|
|
|
17363
17528
|
<div class="label">Overall: ${escapeHtml(report.overall)}</div>
|
|
17364
17529
|
</div>
|
|
17365
17530
|
|
|
17366
|
-
${renderSection("Completeness", report.completeness)}
|
|
17367
|
-
|
|
17368
|
-
|
|
17369
|
-
|
|
17370
|
-
|
|
17371
|
-
|
|
17372
|
-
|
|
17373
|
-
|
|
17374
|
-
|
|
17375
|
-
|
|
17376
|
-
|
|
17377
|
-
|
|
17378
|
-
|
|
17379
|
-
|
|
17380
|
-
|
|
17381
|
-
|
|
17531
|
+
${renderSection("health-completeness", "Completeness", report.completeness)}
|
|
17532
|
+
|
|
17533
|
+
${collapsibleSection(
|
|
17534
|
+
"health-completeness-overview",
|
|
17535
|
+
"Completeness Overview",
|
|
17536
|
+
buildHealthGauge(
|
|
17537
|
+
metrics ? Object.entries(metrics.completeness).map(([name, cat]) => ({
|
|
17538
|
+
name: name.replace(/\b\w/g, (c) => c.toUpperCase()),
|
|
17539
|
+
complete: cat.complete,
|
|
17540
|
+
total: cat.total
|
|
17541
|
+
})) : report.completeness.map((c) => {
|
|
17542
|
+
const match = c.summary.match(/(\d+)\s*\/\s*(\d+)/);
|
|
17543
|
+
return {
|
|
17544
|
+
name: c.name,
|
|
17545
|
+
complete: match ? parseInt(match[1], 10) : 0,
|
|
17546
|
+
total: match ? parseInt(match[2], 10) : 0
|
|
17547
|
+
};
|
|
17548
|
+
})
|
|
17549
|
+
),
|
|
17550
|
+
{ titleClass: "health-section-title" }
|
|
17382
17551
|
)}
|
|
17383
17552
|
|
|
17384
|
-
${renderSection("Process", report.process)}
|
|
17385
|
-
|
|
17386
|
-
|
|
17387
|
-
|
|
17388
|
-
|
|
17389
|
-
"
|
|
17390
|
-
|
|
17391
|
-
|
|
17392
|
-
|
|
17393
|
-
|
|
17394
|
-
|
|
17553
|
+
${renderSection("health-process", "Process", report.process)}
|
|
17554
|
+
|
|
17555
|
+
${collapsibleSection(
|
|
17556
|
+
"health-process-summary",
|
|
17557
|
+
"Process Summary",
|
|
17558
|
+
metrics ? buildStatusPie("Process Health", {
|
|
17559
|
+
Stale: metrics.process.stale.length,
|
|
17560
|
+
"Aging Actions": metrics.process.agingActions.length,
|
|
17561
|
+
Healthy: Math.max(
|
|
17562
|
+
0,
|
|
17563
|
+
(metrics.completeness ? Object.values(metrics.completeness).reduce((sum, c) => sum + c.total, 0) : 0) - metrics.process.stale.length - metrics.process.agingActions.length
|
|
17564
|
+
)
|
|
17565
|
+
}) : "",
|
|
17566
|
+
{ titleClass: "health-section-title" }
|
|
17567
|
+
)}
|
|
17395
17568
|
`;
|
|
17396
17569
|
}
|
|
17397
17570
|
|
|
@@ -17449,7 +17622,7 @@ function timelinePage(diagrams) {
|
|
|
17449
17622
|
return `
|
|
17450
17623
|
<div class="page-header">
|
|
17451
17624
|
<h2>Project Timeline</h2>
|
|
17452
|
-
<div class="subtitle">
|
|
17625
|
+
<div class="subtitle">Epic timeline across sprints</div>
|
|
17453
17626
|
</div>
|
|
17454
17627
|
|
|
17455
17628
|
${buildTimelineGantt(diagrams)}
|
|
@@ -17477,9 +17650,10 @@ function upcomingPage(data) {
|
|
|
17477
17650
|
const hasActions = data.dueSoonActions.length > 0;
|
|
17478
17651
|
const hasSprintTasks = data.dueSoonSprintTasks.length > 0;
|
|
17479
17652
|
const hasTrending = data.trending.length > 0;
|
|
17480
|
-
const actionsTable = hasActions ?
|
|
17481
|
-
|
|
17482
|
-
|
|
17653
|
+
const actionsTable = hasActions ? collapsibleSection(
|
|
17654
|
+
"upcoming-actions",
|
|
17655
|
+
"Due Soon \u2014 Actions",
|
|
17656
|
+
`<div class="table-wrap">
|
|
17483
17657
|
<table>
|
|
17484
17658
|
<thead>
|
|
17485
17659
|
<tr>
|
|
@@ -17494,7 +17668,7 @@ function upcomingPage(data) {
|
|
|
17494
17668
|
</thead>
|
|
17495
17669
|
<tbody>
|
|
17496
17670
|
${data.dueSoonActions.map(
|
|
17497
|
-
|
|
17671
|
+
(a) => `
|
|
17498
17672
|
<tr class="${urgencyRowClass(a.urgency)}">
|
|
17499
17673
|
<td><a href="/docs/action/${escapeHtml(a.id)}">${escapeHtml(a.id)}</a></td>
|
|
17500
17674
|
<td>${escapeHtml(a.title)}</td>
|
|
@@ -17504,13 +17678,16 @@ function upcomingPage(data) {
|
|
|
17504
17678
|
<td>${urgencyBadge(a.urgency)}</td>
|
|
17505
17679
|
<td>${a.relatedTaskCount > 0 ? a.relatedTaskCount : "\u2014"}</td>
|
|
17506
17680
|
</tr>`
|
|
17507
|
-
|
|
17681
|
+
).join("")}
|
|
17508
17682
|
</tbody>
|
|
17509
17683
|
</table>
|
|
17510
|
-
</div
|
|
17511
|
-
|
|
17512
|
-
|
|
17513
|
-
|
|
17684
|
+
</div>`,
|
|
17685
|
+
{ titleTag: "h3" }
|
|
17686
|
+
) : "";
|
|
17687
|
+
const sprintTasksTable = hasSprintTasks ? collapsibleSection(
|
|
17688
|
+
"upcoming-sprint-tasks",
|
|
17689
|
+
"Due Soon \u2014 Sprint Tasks",
|
|
17690
|
+
`<div class="table-wrap">
|
|
17514
17691
|
<table>
|
|
17515
17692
|
<thead>
|
|
17516
17693
|
<tr>
|
|
@@ -17524,7 +17701,7 @@ function upcomingPage(data) {
|
|
|
17524
17701
|
</thead>
|
|
17525
17702
|
<tbody>
|
|
17526
17703
|
${data.dueSoonSprintTasks.map(
|
|
17527
|
-
|
|
17704
|
+
(t) => `
|
|
17528
17705
|
<tr class="${urgencyRowClass(t.urgency)}">
|
|
17529
17706
|
<td><a href="/docs/task/${escapeHtml(t.id)}">${escapeHtml(t.id)}</a></td>
|
|
17530
17707
|
<td>${escapeHtml(t.title)}</td>
|
|
@@ -17533,13 +17710,16 @@ function upcomingPage(data) {
|
|
|
17533
17710
|
<td>${formatDate(t.sprintEndDate)}</td>
|
|
17534
17711
|
<td>${urgencyBadge(t.urgency)}</td>
|
|
17535
17712
|
</tr>`
|
|
17536
|
-
|
|
17713
|
+
).join("")}
|
|
17537
17714
|
</tbody>
|
|
17538
17715
|
</table>
|
|
17539
|
-
</div
|
|
17540
|
-
|
|
17541
|
-
|
|
17542
|
-
|
|
17716
|
+
</div>`,
|
|
17717
|
+
{ titleTag: "h3" }
|
|
17718
|
+
) : "";
|
|
17719
|
+
const trendingTable = hasTrending ? collapsibleSection(
|
|
17720
|
+
"upcoming-trending",
|
|
17721
|
+
"Trending",
|
|
17722
|
+
`<div class="table-wrap">
|
|
17543
17723
|
<table>
|
|
17544
17724
|
<thead>
|
|
17545
17725
|
<tr>
|
|
@@ -17554,7 +17734,7 @@ function upcomingPage(data) {
|
|
|
17554
17734
|
</thead>
|
|
17555
17735
|
<tbody>
|
|
17556
17736
|
${data.trending.map(
|
|
17557
|
-
|
|
17737
|
+
(t, i) => `
|
|
17558
17738
|
<tr>
|
|
17559
17739
|
<td><span class="trending-rank">${i + 1}</span></td>
|
|
17560
17740
|
<td><a href="/docs/${escapeHtml(t.type)}/${escapeHtml(t.id)}">${escapeHtml(t.id)}</a></td>
|
|
@@ -17564,10 +17744,12 @@ function upcomingPage(data) {
|
|
|
17564
17744
|
<td><span class="trending-score">${t.score}</span></td>
|
|
17565
17745
|
<td>${t.signals.map((s) => `<span class="signal-tag">${escapeHtml(s.factor)} +${s.points}</span>`).join(" ")}</td>
|
|
17566
17746
|
</tr>`
|
|
17567
|
-
|
|
17747
|
+
).join("")}
|
|
17568
17748
|
</tbody>
|
|
17569
17749
|
</table>
|
|
17570
|
-
</div
|
|
17750
|
+
</div>`,
|
|
17751
|
+
{ titleTag: "h3" }
|
|
17752
|
+
) : "";
|
|
17571
17753
|
const emptyState = !hasActions && !hasSprintTasks && !hasTrending ? '<div class="empty"><p>No upcoming items or trending activity found.</p></div>' : "";
|
|
17572
17754
|
return `
|
|
17573
17755
|
<div class="page-header">
|
|
@@ -20865,8 +21047,8 @@ function gatherContext(store, focusFeature, includeDecisions = true, includeQues
|
|
|
20865
21047
|
title: e.frontmatter.title,
|
|
20866
21048
|
status: e.frontmatter.status,
|
|
20867
21049
|
linkedFeature: normalizeLinkedFeatures(e.frontmatter.linkedFeature),
|
|
20868
|
-
targetDate: e.frontmatter.targetDate
|
|
20869
|
-
estimatedEffort: e.frontmatter.estimatedEffort
|
|
21050
|
+
targetDate: typeof e.frontmatter.targetDate === "string" ? e.frontmatter.targetDate : null,
|
|
21051
|
+
estimatedEffort: typeof e.frontmatter.estimatedEffort === "string" ? e.frontmatter.estimatedEffort : null,
|
|
20870
21052
|
content: e.content,
|
|
20871
21053
|
linkedTaskCount: tasks.filter(
|
|
20872
21054
|
(t) => normalizeLinkedEpics(t.frontmatter.linkedEpic).includes(e.frontmatter.id)
|
|
@@ -20877,10 +21059,10 @@ function gatherContext(store, focusFeature, includeDecisions = true, includeQues
|
|
|
20877
21059
|
title: t.frontmatter.title,
|
|
20878
21060
|
status: t.frontmatter.status,
|
|
20879
21061
|
linkedEpic: normalizeLinkedEpics(t.frontmatter.linkedEpic),
|
|
20880
|
-
acceptanceCriteria: t.frontmatter.acceptanceCriteria
|
|
20881
|
-
technicalNotes: t.frontmatter.technicalNotes
|
|
20882
|
-
complexity: t.frontmatter.complexity
|
|
20883
|
-
estimatedPoints: t.frontmatter.estimatedPoints
|
|
21062
|
+
acceptanceCriteria: typeof t.frontmatter.acceptanceCriteria === "string" ? t.frontmatter.acceptanceCriteria : null,
|
|
21063
|
+
technicalNotes: typeof t.frontmatter.technicalNotes === "string" ? t.frontmatter.technicalNotes : null,
|
|
21064
|
+
complexity: typeof t.frontmatter.complexity === "string" ? t.frontmatter.complexity : null,
|
|
21065
|
+
estimatedPoints: typeof t.frontmatter.estimatedPoints === "number" ? t.frontmatter.estimatedPoints : null,
|
|
20884
21066
|
priority: t.frontmatter.priority ?? null
|
|
20885
21067
|
})),
|
|
20886
21068
|
decisions: allDecisions.map((d) => ({
|
|
@@ -25241,7 +25423,7 @@ function createProgram() {
|
|
|
25241
25423
|
const program = new Command();
|
|
25242
25424
|
program.name("marvin").description(
|
|
25243
25425
|
"AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
|
|
25244
|
-
).version("0.4.
|
|
25426
|
+
).version("0.4.6");
|
|
25245
25427
|
program.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
|
|
25246
25428
|
await initCommand();
|
|
25247
25429
|
});
|