mrvn-cli 0.5.4 → 0.5.5
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 +444 -33
- package/dist/index.js.map +1 -1
- package/dist/marvin-serve.js +433 -22
- package/dist/marvin-serve.js.map +1 -1
- package/dist/marvin.js +444 -33
- package/dist/marvin.js.map +1 -1
- package/package.json +1 -1
package/dist/marvin.js
CHANGED
|
@@ -14774,16 +14774,13 @@ function collectSprintSummaryData(store, sprintId) {
|
|
|
14774
14774
|
title: d.frontmatter.title,
|
|
14775
14775
|
type: d.frontmatter.type
|
|
14776
14776
|
}));
|
|
14777
|
-
const
|
|
14778
|
-
(d) => !DONE_STATUSES2.has(d.frontmatter.status) && d.frontmatter.tags?.includes("risk") && d.frontmatter.tags?.some((t) => relevantTags.has(t))
|
|
14779
|
-
)
|
|
14780
|
-
|
|
14781
|
-
|
|
14782
|
-
|
|
14783
|
-
|
|
14784
|
-
type: d.frontmatter.type
|
|
14785
|
-
});
|
|
14786
|
-
}
|
|
14777
|
+
const risks = allDocs.filter(
|
|
14778
|
+
(d) => !DONE_STATUSES2.has(d.frontmatter.status) && d.frontmatter.tags?.includes("risk") && d.frontmatter.tags?.some((t) => relevantTags.has(t))
|
|
14779
|
+
).map((d) => ({
|
|
14780
|
+
id: d.frontmatter.id,
|
|
14781
|
+
title: d.frontmatter.title,
|
|
14782
|
+
type: d.frontmatter.type
|
|
14783
|
+
}));
|
|
14787
14784
|
let velocity = null;
|
|
14788
14785
|
const currentRate = workItems.completionPct;
|
|
14789
14786
|
const completedSprints = sprintDocs.filter((s) => DONE_STATUSES2.has(s.frontmatter.status) && s.frontmatter.id !== fm.id).sort((a, b) => (b.frontmatter.endDate ?? "").localeCompare(a.frontmatter.endDate ?? ""));
|
|
@@ -14819,6 +14816,7 @@ function collectSprintSummaryData(store, sprintId) {
|
|
|
14819
14816
|
openActions,
|
|
14820
14817
|
openQuestions,
|
|
14821
14818
|
blockers,
|
|
14819
|
+
risks,
|
|
14822
14820
|
velocity
|
|
14823
14821
|
};
|
|
14824
14822
|
}
|
|
@@ -15404,6 +15402,13 @@ function buildPrompt(data) {
|
|
|
15404
15402
|
sections.push(`- ${b.id} (${b.type}): ${b.title}`);
|
|
15405
15403
|
}
|
|
15406
15404
|
}
|
|
15405
|
+
if (data.risks.length > 0) {
|
|
15406
|
+
sections.push(`
|
|
15407
|
+
## Risks`);
|
|
15408
|
+
for (const r of data.risks) {
|
|
15409
|
+
sections.push(`- ${r.id} (${r.type}): ${r.title}`);
|
|
15410
|
+
}
|
|
15411
|
+
}
|
|
15407
15412
|
if (data.velocity) {
|
|
15408
15413
|
sections.push(`
|
|
15409
15414
|
## Velocity`);
|
|
@@ -18252,7 +18257,7 @@ import * as readline from "readline";
|
|
|
18252
18257
|
import chalk2 from "chalk";
|
|
18253
18258
|
import ora from "ora";
|
|
18254
18259
|
import {
|
|
18255
|
-
query as
|
|
18260
|
+
query as query4
|
|
18256
18261
|
} from "@anthropic-ai/claude-agent-sdk";
|
|
18257
18262
|
|
|
18258
18263
|
// src/personas/prompt-builder.ts
|
|
@@ -18392,9 +18397,9 @@ var DocumentStore = class {
|
|
|
18392
18397
|
}
|
|
18393
18398
|
}
|
|
18394
18399
|
}
|
|
18395
|
-
list(
|
|
18400
|
+
list(query9) {
|
|
18396
18401
|
const results = [];
|
|
18397
|
-
const types =
|
|
18402
|
+
const types = query9?.type ? [query9.type] : Object.keys(this.typeDirs);
|
|
18398
18403
|
for (const type of types) {
|
|
18399
18404
|
const dirName = this.typeDirs[type];
|
|
18400
18405
|
if (!dirName) continue;
|
|
@@ -18405,9 +18410,9 @@ var DocumentStore = class {
|
|
|
18405
18410
|
const filePath = path6.join(dir, file2);
|
|
18406
18411
|
const raw = fs6.readFileSync(filePath, "utf-8");
|
|
18407
18412
|
const doc = parseDocument(raw, filePath);
|
|
18408
|
-
if (
|
|
18409
|
-
if (
|
|
18410
|
-
if (
|
|
18413
|
+
if (query9?.status && doc.frontmatter.status !== query9.status) continue;
|
|
18414
|
+
if (query9?.owner && doc.frontmatter.owner !== query9.owner) continue;
|
|
18415
|
+
if (query9?.tag && (!doc.frontmatter.tags || !doc.frontmatter.tags.includes(query9.tag)))
|
|
18411
18416
|
continue;
|
|
18412
18417
|
results.push(doc);
|
|
18413
18418
|
}
|
|
@@ -18485,14 +18490,19 @@ var DocumentStore = class {
|
|
|
18485
18490
|
if (!existing) {
|
|
18486
18491
|
throw new Error(`Document ${id} not found`);
|
|
18487
18492
|
}
|
|
18493
|
+
const keysToDelete = Object.entries(updates).filter(([, v]) => v === void 0).map(([k]) => k);
|
|
18488
18494
|
const cleanedUpdates = Object.fromEntries(
|
|
18489
18495
|
Object.entries(updates).filter(([, v]) => v !== void 0)
|
|
18490
18496
|
);
|
|
18491
|
-
const
|
|
18497
|
+
const merged = {
|
|
18492
18498
|
...existing.frontmatter,
|
|
18493
18499
|
...cleanedUpdates,
|
|
18494
18500
|
updated: (/* @__PURE__ */ new Date()).toISOString()
|
|
18495
18501
|
};
|
|
18502
|
+
for (const key of keysToDelete) {
|
|
18503
|
+
delete merged[key];
|
|
18504
|
+
}
|
|
18505
|
+
const updatedFrontmatter = merged;
|
|
18496
18506
|
const doc = {
|
|
18497
18507
|
frontmatter: updatedFrontmatter,
|
|
18498
18508
|
content: content ?? existing.content,
|
|
@@ -19777,6 +19787,7 @@ a:hover { text-decoration: underline; }
|
|
|
19777
19787
|
}
|
|
19778
19788
|
|
|
19779
19789
|
.card a { color: inherit; text-decoration: none; display: block; }
|
|
19790
|
+
a.card-link { color: inherit; text-decoration: none; cursor: pointer; }
|
|
19780
19791
|
|
|
19781
19792
|
.card .card-label {
|
|
19782
19793
|
font-size: 0.75rem;
|
|
@@ -20192,6 +20203,68 @@ tr:hover td {
|
|
|
20192
20203
|
.priority-medium { color: var(--amber); }
|
|
20193
20204
|
.priority-low { color: var(--green); }
|
|
20194
20205
|
|
|
20206
|
+
/* Blocker / Risk detail cards */
|
|
20207
|
+
.blocker-card {
|
|
20208
|
+
background: var(--bg-card);
|
|
20209
|
+
border: 1px solid var(--border);
|
|
20210
|
+
border-radius: var(--radius);
|
|
20211
|
+
padding: 1.25rem;
|
|
20212
|
+
margin-bottom: 1rem;
|
|
20213
|
+
}
|
|
20214
|
+
.blocker-card-header {
|
|
20215
|
+
display: flex;
|
|
20216
|
+
align-items: center;
|
|
20217
|
+
gap: 0.5rem;
|
|
20218
|
+
font-size: 0.85rem;
|
|
20219
|
+
margin-bottom: 0.25rem;
|
|
20220
|
+
}
|
|
20221
|
+
.blocker-card-title {
|
|
20222
|
+
margin: 0.25rem 0 0.5rem;
|
|
20223
|
+
font-size: 1rem;
|
|
20224
|
+
}
|
|
20225
|
+
.blocker-card-meta {
|
|
20226
|
+
display: flex;
|
|
20227
|
+
flex-wrap: wrap;
|
|
20228
|
+
gap: 1rem;
|
|
20229
|
+
font-size: 0.85rem;
|
|
20230
|
+
color: var(--text-dim);
|
|
20231
|
+
margin-bottom: 0.75rem;
|
|
20232
|
+
}
|
|
20233
|
+
.blocker-card-content {
|
|
20234
|
+
border-top: 1px solid var(--border);
|
|
20235
|
+
padding-top: 0.75rem;
|
|
20236
|
+
font-size: 0.9rem;
|
|
20237
|
+
}
|
|
20238
|
+
.risk-assessment-content {
|
|
20239
|
+
background: var(--bg);
|
|
20240
|
+
border: 1px solid var(--border);
|
|
20241
|
+
border-left: 3px solid var(--amber);
|
|
20242
|
+
border-radius: var(--radius);
|
|
20243
|
+
padding: 1rem 1.25rem;
|
|
20244
|
+
margin-top: 0.75rem;
|
|
20245
|
+
font-size: 0.9rem;
|
|
20246
|
+
}
|
|
20247
|
+
.risk-assess-btn {
|
|
20248
|
+
font-size: 0.8rem;
|
|
20249
|
+
padding: 0.4rem 0.8rem;
|
|
20250
|
+
margin-top: 0.75rem;
|
|
20251
|
+
}
|
|
20252
|
+
.risk-assess-loading {
|
|
20253
|
+
margin-top: 0.75rem;
|
|
20254
|
+
font-size: 0.85rem;
|
|
20255
|
+
}
|
|
20256
|
+
.risk-assess-error {
|
|
20257
|
+
margin-top: 0.5rem;
|
|
20258
|
+
}
|
|
20259
|
+
.risk-assessment-label {
|
|
20260
|
+
font-size: 0.7rem;
|
|
20261
|
+
text-transform: uppercase;
|
|
20262
|
+
letter-spacing: 0.08em;
|
|
20263
|
+
color: var(--amber);
|
|
20264
|
+
font-weight: 600;
|
|
20265
|
+
margin-bottom: 0.5rem;
|
|
20266
|
+
}
|
|
20267
|
+
|
|
20195
20268
|
/* Health */
|
|
20196
20269
|
.health-section-title {
|
|
20197
20270
|
font-size: 1.1rem;
|
|
@@ -21308,6 +21381,134 @@ function personaPickerPage() {
|
|
|
21308
21381
|
</div>`;
|
|
21309
21382
|
}
|
|
21310
21383
|
|
|
21384
|
+
// src/reports/sprint-summary/risk-assessment.ts
|
|
21385
|
+
import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
|
|
21386
|
+
var SYSTEM_PROMPT2 = `You are a delivery management assistant generating a data-driven risk assessment.
|
|
21387
|
+
|
|
21388
|
+
IMPORTANT: All the data you need is provided in the user message below. Do NOT attempt to look up, search for, or request additional information. Analyze ONLY the data given and produce your assessment immediately.
|
|
21389
|
+
|
|
21390
|
+
Produce a concise markdown assessment with these sections:
|
|
21391
|
+
|
|
21392
|
+
## Status Assessment
|
|
21393
|
+
One-line verdict: is this risk actively being mitigated, stalled, or escalating?
|
|
21394
|
+
|
|
21395
|
+
## Related Activity
|
|
21396
|
+
What actions, decisions, or contributions are connected to this risk? How are they progressing? Be specific \u2014 reference artifact IDs from the data provided.
|
|
21397
|
+
|
|
21398
|
+
## Trajectory
|
|
21399
|
+
Based on the data (status of related items, time remaining, ownership), is this risk trending toward resolution or toward becoming a blocker? Explain your reasoning with concrete evidence.
|
|
21400
|
+
|
|
21401
|
+
## Recommendation
|
|
21402
|
+
One concrete next step to move this risk toward resolution.
|
|
21403
|
+
|
|
21404
|
+
Rules:
|
|
21405
|
+
- Reference artifact IDs, dates, owners, and statuses from the provided data
|
|
21406
|
+
- Keep the tone professional and direct
|
|
21407
|
+
- Do NOT speculate beyond what the data supports \u2014 if information is insufficient, say so explicitly
|
|
21408
|
+
- Do NOT ask for more information or say you will look things up \u2014 everything you need is in the prompt
|
|
21409
|
+
- Produce the full assessment text directly`;
|
|
21410
|
+
async function generateRiskAssessment(data, riskId, store) {
|
|
21411
|
+
const risk = data.risks.find((r) => r.id === riskId);
|
|
21412
|
+
if (!risk) return "Risk not found in sprint data.";
|
|
21413
|
+
const prompt = buildSingleRiskPrompt(data, risk, store);
|
|
21414
|
+
const result = query2({
|
|
21415
|
+
prompt,
|
|
21416
|
+
options: {
|
|
21417
|
+
systemPrompt: SYSTEM_PROMPT2,
|
|
21418
|
+
maxTurns: 1,
|
|
21419
|
+
tools: [],
|
|
21420
|
+
allowedTools: []
|
|
21421
|
+
}
|
|
21422
|
+
});
|
|
21423
|
+
for await (const msg of result) {
|
|
21424
|
+
if (msg.type === "assistant") {
|
|
21425
|
+
const text = msg.message.content.find(
|
|
21426
|
+
(b) => b.type === "text"
|
|
21427
|
+
);
|
|
21428
|
+
if (text) return text.text;
|
|
21429
|
+
}
|
|
21430
|
+
}
|
|
21431
|
+
return "Unable to generate risk assessment.";
|
|
21432
|
+
}
|
|
21433
|
+
function buildSingleRiskPrompt(data, risk, store) {
|
|
21434
|
+
const sections = [];
|
|
21435
|
+
sections.push(`# Sprint: ${data.sprint.id} \u2014 ${data.sprint.title}`);
|
|
21436
|
+
if (data.sprint.goal) sections.push(`Goal: ${data.sprint.goal}`);
|
|
21437
|
+
sections.push(`Days remaining: ${data.timeline.daysRemaining} / ${data.timeline.totalDays}`);
|
|
21438
|
+
sections.push(`Completion: ${data.workItems.completionPct}%`);
|
|
21439
|
+
sections.push("");
|
|
21440
|
+
const doc = store.get(risk.id);
|
|
21441
|
+
sections.push(`# RISK: ${risk.id} \u2014 ${risk.title}`);
|
|
21442
|
+
sections.push(`Type: ${risk.type}`);
|
|
21443
|
+
if (doc) {
|
|
21444
|
+
sections.push(`Status: ${doc.frontmatter.status}`);
|
|
21445
|
+
if (doc.frontmatter.owner) sections.push(`Owner: ${doc.frontmatter.owner}`);
|
|
21446
|
+
if (doc.frontmatter.assignee) sections.push(`Assignee: ${doc.frontmatter.assignee}`);
|
|
21447
|
+
if (doc.frontmatter.priority) sections.push(`Priority: ${doc.frontmatter.priority}`);
|
|
21448
|
+
if (doc.frontmatter.dueDate) sections.push(`Due date: ${doc.frontmatter.dueDate}`);
|
|
21449
|
+
if (doc.frontmatter.created) sections.push(`Created: ${doc.frontmatter.created.slice(0, 10)}`);
|
|
21450
|
+
const tags = doc.frontmatter.tags ?? [];
|
|
21451
|
+
if (tags.length > 0) sections.push(`Tags: ${tags.join(", ")}`);
|
|
21452
|
+
if (doc.content.trim()) {
|
|
21453
|
+
sections.push(`
|
|
21454
|
+
Description:
|
|
21455
|
+
${doc.content.trim()}`);
|
|
21456
|
+
}
|
|
21457
|
+
const allDocs = store.list();
|
|
21458
|
+
const relatedIds = /* @__PURE__ */ new Set();
|
|
21459
|
+
for (const d of allDocs) {
|
|
21460
|
+
if (d.frontmatter.aboutArtifact === risk.id) {
|
|
21461
|
+
relatedIds.add(d.frontmatter.id);
|
|
21462
|
+
}
|
|
21463
|
+
}
|
|
21464
|
+
const idPattern = /\b([A-Z]-\d{3,})\b/g;
|
|
21465
|
+
let match;
|
|
21466
|
+
while ((match = idPattern.exec(doc.content)) !== null) {
|
|
21467
|
+
relatedIds.add(match[1]);
|
|
21468
|
+
}
|
|
21469
|
+
const significantTags = tags.filter(
|
|
21470
|
+
(t) => !t.startsWith("sprint:") && !t.startsWith("focus:") && t !== "risk"
|
|
21471
|
+
);
|
|
21472
|
+
if (significantTags.length > 0) {
|
|
21473
|
+
for (const d of allDocs) {
|
|
21474
|
+
if (d.frontmatter.id === risk.id) continue;
|
|
21475
|
+
const dTags = d.frontmatter.tags ?? [];
|
|
21476
|
+
if (significantTags.some((t) => dTags.includes(t))) {
|
|
21477
|
+
relatedIds.add(d.frontmatter.id);
|
|
21478
|
+
}
|
|
21479
|
+
}
|
|
21480
|
+
}
|
|
21481
|
+
const about = doc.frontmatter.aboutArtifact;
|
|
21482
|
+
if (about) {
|
|
21483
|
+
relatedIds.add(about);
|
|
21484
|
+
for (const d of allDocs) {
|
|
21485
|
+
if (d.frontmatter.aboutArtifact === about && d.frontmatter.id !== risk.id) {
|
|
21486
|
+
relatedIds.add(d.frontmatter.id);
|
|
21487
|
+
}
|
|
21488
|
+
}
|
|
21489
|
+
}
|
|
21490
|
+
const relatedDocs = [...relatedIds].map((id) => store.get(id)).filter((d) => d != null).slice(0, 20);
|
|
21491
|
+
if (relatedDocs.length > 0) {
|
|
21492
|
+
sections.push(`
|
|
21493
|
+
## Related Documents (${relatedDocs.length})`);
|
|
21494
|
+
for (const rd of relatedDocs) {
|
|
21495
|
+
const owner = rd.frontmatter.owner ?? "unowned";
|
|
21496
|
+
const summary = rd.content.trim().slice(0, 300);
|
|
21497
|
+
sections.push(
|
|
21498
|
+
`- ${rd.frontmatter.id} (${rd.frontmatter.type}) [${rd.frontmatter.status}] \u2014 ${rd.frontmatter.title}`
|
|
21499
|
+
);
|
|
21500
|
+
sections.push(` Owner: ${owner}${rd.frontmatter.dueDate ? `, Due: ${rd.frontmatter.dueDate}` : ""}`);
|
|
21501
|
+
if (summary) sections.push(` Summary: ${summary}${rd.content.trim().length > 300 ? "..." : ""}`);
|
|
21502
|
+
}
|
|
21503
|
+
}
|
|
21504
|
+
}
|
|
21505
|
+
sections.push("");
|
|
21506
|
+
sections.push(`---`);
|
|
21507
|
+
sections.push(`
|
|
21508
|
+
Generate the risk assessment for ${risk.id} based on the data above.`);
|
|
21509
|
+
return sections.join("\n");
|
|
21510
|
+
}
|
|
21511
|
+
|
|
21311
21512
|
// src/web/templates/persona-switcher.ts
|
|
21312
21513
|
function renderPersonaSwitcher(current, _currentPath) {
|
|
21313
21514
|
const views = getAllPersonaViews();
|
|
@@ -22567,11 +22768,16 @@ function sprintSummaryPage(data, cached2) {
|
|
|
22567
22768
|
<div class="card-value">${data.linkedEpics.length}</div>
|
|
22568
22769
|
<div class="card-sub">linked to sprint</div>
|
|
22569
22770
|
</div>
|
|
22570
|
-
<
|
|
22771
|
+
<a class="card card-link" href="sprint-blockers">
|
|
22571
22772
|
<div class="card-label">Blockers</div>
|
|
22572
22773
|
<div class="card-value${data.blockers.length > 0 ? " priority-high" : ""}">${data.blockers.length}</div>
|
|
22573
|
-
<div class="card-sub">${data.
|
|
22574
|
-
</
|
|
22774
|
+
<div class="card-sub">${data.workItems.blocked} blocked items</div>
|
|
22775
|
+
</a>
|
|
22776
|
+
<a class="card card-link" href="sprint-risks">
|
|
22777
|
+
<div class="card-label">Risks</div>
|
|
22778
|
+
<div class="card-value${data.risks.length > 0 ? " priority-medium" : ""}">${data.risks.length}</div>
|
|
22779
|
+
<div class="card-sub">open risk items</div>
|
|
22780
|
+
</a>
|
|
22575
22781
|
</div>`;
|
|
22576
22782
|
const epicsTable = data.linkedEpics.length > 0 ? collapsibleSection(
|
|
22577
22783
|
"ss-epics",
|
|
@@ -23961,6 +24167,168 @@ function upcomingPage(data) {
|
|
|
23961
24167
|
`;
|
|
23962
24168
|
}
|
|
23963
24169
|
|
|
24170
|
+
// src/web/templates/pages/sprint-blockers.ts
|
|
24171
|
+
function sprintBlockersPage(data, store) {
|
|
24172
|
+
if (!data) {
|
|
24173
|
+
return `
|
|
24174
|
+
<div class="page-header">
|
|
24175
|
+
<h2>Sprint Blockers</h2>
|
|
24176
|
+
<div class="subtitle">Blocked items in the active sprint</div>
|
|
24177
|
+
</div>
|
|
24178
|
+
<div class="empty">
|
|
24179
|
+
<h3>No Active Sprint</h3>
|
|
24180
|
+
<p>No active sprint found.</p>
|
|
24181
|
+
</div>`;
|
|
24182
|
+
}
|
|
24183
|
+
const blockerDocs = data.blockers.map((b) => {
|
|
24184
|
+
const doc = store.get(b.id);
|
|
24185
|
+
return { ...b, doc };
|
|
24186
|
+
});
|
|
24187
|
+
const statsCards = `
|
|
24188
|
+
<div class="cards">
|
|
24189
|
+
<div class="card">
|
|
24190
|
+
<div class="card-label">Blocked Items</div>
|
|
24191
|
+
<div class="card-value${blockerDocs.length > 0 ? " priority-high" : ""}">${blockerDocs.length}</div>
|
|
24192
|
+
<div class="card-sub">in ${escapeHtml(data.sprint.id)}</div>
|
|
24193
|
+
</div>
|
|
24194
|
+
</div>`;
|
|
24195
|
+
const itemCards = blockerDocs.map((b) => {
|
|
24196
|
+
const doc = b.doc;
|
|
24197
|
+
const owner = doc?.frontmatter.owner;
|
|
24198
|
+
const assignee = doc?.frontmatter.assignee;
|
|
24199
|
+
const content = doc?.content?.trim();
|
|
24200
|
+
return `
|
|
24201
|
+
<div class="blocker-card">
|
|
24202
|
+
<div class="blocker-card-header">
|
|
24203
|
+
<a href="/docs/${escapeHtml(b.type)}/${escapeHtml(b.id)}">${escapeHtml(b.id)}</a>
|
|
24204
|
+
<span class="text-dim">${escapeHtml(typeLabel(b.type))}</span>
|
|
24205
|
+
${statusBadge("blocked")}
|
|
24206
|
+
</div>
|
|
24207
|
+
<h4 class="blocker-card-title">${escapeHtml(b.title)}</h4>
|
|
24208
|
+
<div class="blocker-card-meta">
|
|
24209
|
+
${owner ? `<span><strong>Owner:</strong> ${escapeHtml(owner)}</span>` : ""}
|
|
24210
|
+
${assignee ? `<span><strong>Assignee:</strong> ${escapeHtml(assignee)}</span>` : ""}
|
|
24211
|
+
${doc?.frontmatter.created ? `<span><strong>Created:</strong> ${formatDate(doc.frontmatter.created)}</span>` : ""}
|
|
24212
|
+
</div>
|
|
24213
|
+
${content ? `<div class="blocker-card-content detail-content">${renderMarkdown(content)}</div>` : ""}
|
|
24214
|
+
</div>`;
|
|
24215
|
+
}).join("");
|
|
24216
|
+
const emptyMessage = blockerDocs.length === 0 ? `<div class="empty"><h3>No Blockers</h3><p>No blocked items in this sprint.</p></div>` : "";
|
|
24217
|
+
return `
|
|
24218
|
+
<div class="page-header">
|
|
24219
|
+
<h2>Sprint Blockers</h2>
|
|
24220
|
+
<div class="subtitle">Blocked items in ${escapeHtml(data.sprint.id)} \u2014 ${escapeHtml(data.sprint.title)}</div>
|
|
24221
|
+
</div>
|
|
24222
|
+
${statsCards}
|
|
24223
|
+
${emptyMessage}
|
|
24224
|
+
${itemCards}`;
|
|
24225
|
+
}
|
|
24226
|
+
|
|
24227
|
+
// src/web/templates/pages/sprint-risks.ts
|
|
24228
|
+
function sprintRisksPage(data, store) {
|
|
24229
|
+
if (!data) {
|
|
24230
|
+
return `
|
|
24231
|
+
<div class="page-header">
|
|
24232
|
+
<h2>Sprint Risks</h2>
|
|
24233
|
+
<div class="subtitle">Risk items in the active sprint</div>
|
|
24234
|
+
</div>
|
|
24235
|
+
<div class="empty">
|
|
24236
|
+
<h3>No Active Sprint</h3>
|
|
24237
|
+
<p>No active sprint found.</p>
|
|
24238
|
+
</div>`;
|
|
24239
|
+
}
|
|
24240
|
+
const riskDocs = data.risks.map((r) => {
|
|
24241
|
+
const doc = store.get(r.id);
|
|
24242
|
+
return { ...r, doc };
|
|
24243
|
+
});
|
|
24244
|
+
const statsCards = `
|
|
24245
|
+
<div class="cards">
|
|
24246
|
+
<div class="card">
|
|
24247
|
+
<div class="card-label">Open Risks</div>
|
|
24248
|
+
<div class="card-value${riskDocs.length > 0 ? " priority-medium" : ""}">${riskDocs.length}</div>
|
|
24249
|
+
<div class="card-sub">in ${escapeHtml(data.sprint.id)}</div>
|
|
24250
|
+
</div>
|
|
24251
|
+
</div>`;
|
|
24252
|
+
const itemCards = riskDocs.map((r) => {
|
|
24253
|
+
const doc = r.doc;
|
|
24254
|
+
const owner = doc?.frontmatter.owner;
|
|
24255
|
+
const assignee = doc?.frontmatter.assignee;
|
|
24256
|
+
const content = doc?.content?.trim();
|
|
24257
|
+
return `
|
|
24258
|
+
<div class="blocker-card" id="risk-${escapeHtml(r.id)}">
|
|
24259
|
+
<div class="blocker-card-header">
|
|
24260
|
+
<a href="/docs/${escapeHtml(r.type)}/${escapeHtml(r.id)}">${escapeHtml(r.id)}</a>
|
|
24261
|
+
<span class="text-dim">${escapeHtml(typeLabel(r.type))}</span>
|
|
24262
|
+
${statusBadge(doc?.frontmatter.status ?? "open")}
|
|
24263
|
+
</div>
|
|
24264
|
+
<h4 class="blocker-card-title">${escapeHtml(r.title)}</h4>
|
|
24265
|
+
<div class="blocker-card-meta">
|
|
24266
|
+
${owner ? `<span><strong>Owner:</strong> ${escapeHtml(owner)}</span>` : ""}
|
|
24267
|
+
${assignee ? `<span><strong>Assignee:</strong> ${escapeHtml(assignee)}</span>` : ""}
|
|
24268
|
+
${doc?.frontmatter.created ? `<span><strong>Created:</strong> ${formatDate(doc.frontmatter.created)}</span>` : ""}
|
|
24269
|
+
</div>
|
|
24270
|
+
${content ? `<div class="blocker-card-content detail-content">${renderMarkdown(content)}</div>` : ""}
|
|
24271
|
+
<div class="risk-assessment" id="assessment-${escapeHtml(r.id)}">
|
|
24272
|
+
<button class="sprint-generate-btn risk-assess-btn" onclick="generateAssessment('${escapeHtml(r.id)}', this)">Assess Risk</button>
|
|
24273
|
+
<div class="sprint-loading risk-assess-loading" style="display:none">
|
|
24274
|
+
<div class="sprint-spinner"></div>
|
|
24275
|
+
<span>Analyzing...</span>
|
|
24276
|
+
</div>
|
|
24277
|
+
<div class="sprint-error risk-assess-error" style="display:none"></div>
|
|
24278
|
+
<div class="risk-assessment-content detail-content" style="display:none"></div>
|
|
24279
|
+
</div>
|
|
24280
|
+
</div>`;
|
|
24281
|
+
}).join("");
|
|
24282
|
+
const emptyMessage = riskDocs.length === 0 ? `<div class="empty"><h3>No Risks</h3><p>No open risk items in this sprint.</p></div>` : "";
|
|
24283
|
+
const script = riskDocs.length > 0 ? `
|
|
24284
|
+
<script>
|
|
24285
|
+
async function generateAssessment(riskId, btn) {
|
|
24286
|
+
var container = document.getElementById('assessment-' + riskId);
|
|
24287
|
+
var loading = container.querySelector('.risk-assess-loading');
|
|
24288
|
+
var errorEl = container.querySelector('.risk-assess-error');
|
|
24289
|
+
var contentEl = container.querySelector('.risk-assessment-content');
|
|
24290
|
+
|
|
24291
|
+
btn.disabled = true;
|
|
24292
|
+
btn.style.display = 'none';
|
|
24293
|
+
loading.style.display = 'flex';
|
|
24294
|
+
errorEl.style.display = 'none';
|
|
24295
|
+
|
|
24296
|
+
try {
|
|
24297
|
+
var res = await fetch('/api/risk-assessment', {
|
|
24298
|
+
method: 'POST',
|
|
24299
|
+
headers: { 'Content-Type': 'application/json' },
|
|
24300
|
+
body: JSON.stringify({ sprintId: '${escapeHtml(data.sprint.id)}', riskId: riskId })
|
|
24301
|
+
});
|
|
24302
|
+
var json = await res.json();
|
|
24303
|
+
if (!res.ok) throw new Error(json.error || 'Failed to generate assessment');
|
|
24304
|
+
|
|
24305
|
+
contentEl.innerHTML = '<div class="risk-assessment-label">AI Assessment</div>' + json.html;
|
|
24306
|
+
contentEl.style.display = 'block';
|
|
24307
|
+
|
|
24308
|
+
loading.style.display = 'none';
|
|
24309
|
+
btn.textContent = 'Regenerate';
|
|
24310
|
+
btn.style.display = '';
|
|
24311
|
+
btn.disabled = false;
|
|
24312
|
+
} catch (e) {
|
|
24313
|
+
loading.style.display = 'none';
|
|
24314
|
+
errorEl.textContent = e.message;
|
|
24315
|
+
errorEl.style.display = 'block';
|
|
24316
|
+
btn.style.display = '';
|
|
24317
|
+
btn.disabled = false;
|
|
24318
|
+
}
|
|
24319
|
+
}
|
|
24320
|
+
</script>` : "";
|
|
24321
|
+
return `
|
|
24322
|
+
<div class="page-header">
|
|
24323
|
+
<h2>Sprint Risks</h2>
|
|
24324
|
+
<div class="subtitle">Risk items in ${escapeHtml(data.sprint.id)} \u2014 ${escapeHtml(data.sprint.title)}</div>
|
|
24325
|
+
</div>
|
|
24326
|
+
${statsCards}
|
|
24327
|
+
${emptyMessage}
|
|
24328
|
+
${itemCards}
|
|
24329
|
+
${script}`;
|
|
24330
|
+
}
|
|
24331
|
+
|
|
23964
24332
|
// src/web/templates/pages/shared-wrappers.ts
|
|
23965
24333
|
function sharedTimelinePage(ctx) {
|
|
23966
24334
|
const diagrams = getDiagramData(ctx.store);
|
|
@@ -23990,6 +24358,16 @@ function sharedSprintSummaryPage(ctx) {
|
|
|
23990
24358
|
const data = getSprintSummaryData(ctx.store, sprintId);
|
|
23991
24359
|
return sprintSummaryPage(data);
|
|
23992
24360
|
}
|
|
24361
|
+
function sharedSprintBlockersPage(ctx) {
|
|
24362
|
+
const sprintId = ctx.searchParams?.get("sprint") ?? void 0;
|
|
24363
|
+
const data = getSprintSummaryData(ctx.store, sprintId);
|
|
24364
|
+
return sprintBlockersPage(data, ctx.store);
|
|
24365
|
+
}
|
|
24366
|
+
function sharedSprintRisksPage(ctx) {
|
|
24367
|
+
const sprintId = ctx.searchParams?.get("sprint") ?? void 0;
|
|
24368
|
+
const data = getSprintSummaryData(ctx.store, sprintId);
|
|
24369
|
+
return sprintRisksPage(data, ctx.store);
|
|
24370
|
+
}
|
|
23993
24371
|
|
|
23994
24372
|
// src/web/shared-page-registration.ts
|
|
23995
24373
|
var SHARED_PAGES = [
|
|
@@ -23998,7 +24376,9 @@ var SHARED_PAGES = [
|
|
|
23998
24376
|
{ pageId: "upcoming", renderer: sharedUpcomingPage },
|
|
23999
24377
|
{ pageId: "gar", renderer: sharedGarPage },
|
|
24000
24378
|
{ pageId: "health", renderer: sharedHealthPage },
|
|
24001
|
-
{ pageId: "sprint-summary", renderer: sharedSprintSummaryPage }
|
|
24379
|
+
{ pageId: "sprint-summary", renderer: sharedSprintSummaryPage },
|
|
24380
|
+
{ pageId: "sprint-blockers", renderer: sharedSprintBlockersPage },
|
|
24381
|
+
{ pageId: "sprint-risks", renderer: sharedSprintRisksPage }
|
|
24002
24382
|
];
|
|
24003
24383
|
for (const persona of ["po", "dm", "tl"]) {
|
|
24004
24384
|
for (const { pageId, renderer } of SHARED_PAGES) {
|
|
@@ -24199,6 +24579,37 @@ function handleRequest(req, res, store, projectName, navGroups) {
|
|
|
24199
24579
|
});
|
|
24200
24580
|
return;
|
|
24201
24581
|
}
|
|
24582
|
+
if (pathname === "/api/risk-assessment" && req.method === "POST") {
|
|
24583
|
+
let bodyStr = "";
|
|
24584
|
+
req.on("data", (chunk) => {
|
|
24585
|
+
bodyStr += chunk;
|
|
24586
|
+
});
|
|
24587
|
+
req.on("end", async () => {
|
|
24588
|
+
try {
|
|
24589
|
+
const { sprintId, riskId } = JSON.parse(bodyStr || "{}");
|
|
24590
|
+
if (!riskId) {
|
|
24591
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
24592
|
+
res.end(JSON.stringify({ error: "riskId is required" }));
|
|
24593
|
+
return;
|
|
24594
|
+
}
|
|
24595
|
+
const data = getSprintSummaryData(store, sprintId);
|
|
24596
|
+
if (!data) {
|
|
24597
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
24598
|
+
res.end(JSON.stringify({ error: "Sprint not found" }));
|
|
24599
|
+
return;
|
|
24600
|
+
}
|
|
24601
|
+
const markdown = await generateRiskAssessment(data, riskId, store);
|
|
24602
|
+
const html = renderMarkdown(markdown);
|
|
24603
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
24604
|
+
res.end(JSON.stringify({ riskId, html }));
|
|
24605
|
+
} catch (err) {
|
|
24606
|
+
console.error("[marvin web] Risk assessment generation error:", err);
|
|
24607
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
24608
|
+
res.end(JSON.stringify({ error: "Failed to generate risk assessment" }));
|
|
24609
|
+
}
|
|
24610
|
+
});
|
|
24611
|
+
return;
|
|
24612
|
+
}
|
|
24202
24613
|
const detailMatch = pathname.match(/^\/docs\/([^/]+)\/([^/]+)$/);
|
|
24203
24614
|
if (detailMatch) {
|
|
24204
24615
|
const [, type, id] = detailMatch;
|
|
@@ -26281,11 +26692,11 @@ function createMarvinMcpServer(store, options) {
|
|
|
26281
26692
|
}
|
|
26282
26693
|
|
|
26283
26694
|
// src/agent/session-namer.ts
|
|
26284
|
-
import { query as
|
|
26695
|
+
import { query as query3 } from "@anthropic-ai/claude-agent-sdk";
|
|
26285
26696
|
async function generateSessionName(turns) {
|
|
26286
26697
|
try {
|
|
26287
26698
|
const transcript = turns.slice(-20).map((t) => `${t.role}: ${t.content.slice(0, 200)}`).join("\n");
|
|
26288
|
-
const result =
|
|
26699
|
+
const result = query3({
|
|
26289
26700
|
prompt: `Summarize this conversation in 3-5 words as a kebab-case name suitable for a filename. Output ONLY the name, nothing else.
|
|
26290
26701
|
|
|
26291
26702
|
${transcript}`,
|
|
@@ -26563,7 +26974,7 @@ Marvin \u2014 ${persona.name}
|
|
|
26563
26974
|
if (existingSession) {
|
|
26564
26975
|
queryOptions.resume = existingSession.id;
|
|
26565
26976
|
}
|
|
26566
|
-
const conversation =
|
|
26977
|
+
const conversation = query4({
|
|
26567
26978
|
prompt,
|
|
26568
26979
|
options: queryOptions
|
|
26569
26980
|
});
|
|
@@ -26922,7 +27333,7 @@ import * as fs12 from "fs";
|
|
|
26922
27333
|
import * as path12 from "path";
|
|
26923
27334
|
import chalk7 from "chalk";
|
|
26924
27335
|
import ora2 from "ora";
|
|
26925
|
-
import { query as
|
|
27336
|
+
import { query as query5 } from "@anthropic-ai/claude-agent-sdk";
|
|
26926
27337
|
|
|
26927
27338
|
// src/sources/prompts.ts
|
|
26928
27339
|
function buildIngestSystemPrompt(persona, projectConfig, isDraft) {
|
|
@@ -27055,7 +27466,7 @@ async function ingestFile(options) {
|
|
|
27055
27466
|
const spinner = ora2({ text: `Analyzing ${fileName}...`, color: "cyan" });
|
|
27056
27467
|
spinner.start();
|
|
27057
27468
|
try {
|
|
27058
|
-
const conversation =
|
|
27469
|
+
const conversation = query5({
|
|
27059
27470
|
prompt: userPrompt,
|
|
27060
27471
|
options: {
|
|
27061
27472
|
systemPrompt,
|
|
@@ -27585,7 +27996,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
27585
27996
|
import { tool as tool24 } from "@anthropic-ai/claude-agent-sdk";
|
|
27586
27997
|
|
|
27587
27998
|
// src/skills/action-runner.ts
|
|
27588
|
-
import { query as
|
|
27999
|
+
import { query as query6 } from "@anthropic-ai/claude-agent-sdk";
|
|
27589
28000
|
var GOVERNANCE_TOOL_NAMES2 = [
|
|
27590
28001
|
"mcp__marvin-governance__list_decisions",
|
|
27591
28002
|
"mcp__marvin-governance__get_decision",
|
|
@@ -27607,7 +28018,7 @@ async function runSkillAction(action, userPrompt, context) {
|
|
|
27607
28018
|
try {
|
|
27608
28019
|
const mcpServer = createMarvinMcpServer(context.store);
|
|
27609
28020
|
const allowedTools = action.allowGovernanceTools !== false ? GOVERNANCE_TOOL_NAMES2 : [];
|
|
27610
|
-
const conversation =
|
|
28021
|
+
const conversation = query6({
|
|
27611
28022
|
prompt: userPrompt,
|
|
27612
28023
|
options: {
|
|
27613
28024
|
systemPrompt: action.systemPrompt,
|
|
@@ -28648,7 +29059,7 @@ import chalk13 from "chalk";
|
|
|
28648
29059
|
// src/analysis/analyze.ts
|
|
28649
29060
|
import chalk12 from "chalk";
|
|
28650
29061
|
import ora4 from "ora";
|
|
28651
|
-
import { query as
|
|
29062
|
+
import { query as query7 } from "@anthropic-ai/claude-agent-sdk";
|
|
28652
29063
|
|
|
28653
29064
|
// src/analysis/prompts.ts
|
|
28654
29065
|
function buildAnalyzeSystemPrompt(persona, projectConfig, isDraft) {
|
|
@@ -28778,7 +29189,7 @@ async function analyzeMeeting(options) {
|
|
|
28778
29189
|
const spinner = ora4({ text: `Analyzing meeting ${meetingId}...`, color: "cyan" });
|
|
28779
29190
|
spinner.start();
|
|
28780
29191
|
try {
|
|
28781
|
-
const conversation =
|
|
29192
|
+
const conversation = query7({
|
|
28782
29193
|
prompt: userPrompt,
|
|
28783
29194
|
options: {
|
|
28784
29195
|
systemPrompt,
|
|
@@ -28905,7 +29316,7 @@ import chalk15 from "chalk";
|
|
|
28905
29316
|
// src/contributions/contribute.ts
|
|
28906
29317
|
import chalk14 from "chalk";
|
|
28907
29318
|
import ora5 from "ora";
|
|
28908
|
-
import { query as
|
|
29319
|
+
import { query as query8 } from "@anthropic-ai/claude-agent-sdk";
|
|
28909
29320
|
|
|
28910
29321
|
// src/contributions/prompts.ts
|
|
28911
29322
|
function buildContributeSystemPrompt(persona, contributionType, projectConfig, isDraft) {
|
|
@@ -29159,7 +29570,7 @@ async function contributeFromPersona(options) {
|
|
|
29159
29570
|
"mcp__marvin-governance__get_action",
|
|
29160
29571
|
"mcp__marvin-governance__get_question"
|
|
29161
29572
|
];
|
|
29162
|
-
const conversation =
|
|
29573
|
+
const conversation = query8({
|
|
29163
29574
|
prompt: userPrompt,
|
|
29164
29575
|
options: {
|
|
29165
29576
|
systemPrompt,
|
|
@@ -29655,7 +30066,7 @@ function createProgram() {
|
|
|
29655
30066
|
const program2 = new Command();
|
|
29656
30067
|
program2.name("marvin").description(
|
|
29657
30068
|
"AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
|
|
29658
|
-
).version("0.5.
|
|
30069
|
+
).version("0.5.5");
|
|
29659
30070
|
program2.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
|
|
29660
30071
|
await initCommand();
|
|
29661
30072
|
});
|