mrvn-cli 0.3.3 → 0.3.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/README.md +303 -147
- package/dist/index.d.ts +1 -0
- package/dist/index.js +956 -85
- package/dist/index.js.map +1 -1
- package/dist/marvin-serve.js +835 -83
- package/dist/marvin-serve.js.map +1 -1
- package/dist/marvin.js +956 -85
- package/dist/marvin.js.map +1 -1
- package/package.json +1 -1
package/dist/marvin.js
CHANGED
|
@@ -13942,7 +13942,7 @@ function createMeetingTools(store) {
|
|
|
13942
13942
|
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
13943
13943
|
};
|
|
13944
13944
|
},
|
|
13945
|
-
{ annotations: {
|
|
13945
|
+
{ annotations: { readOnlyHint: true } }
|
|
13946
13946
|
),
|
|
13947
13947
|
tool(
|
|
13948
13948
|
"get_meeting",
|
|
@@ -13969,7 +13969,7 @@ function createMeetingTools(store) {
|
|
|
13969
13969
|
]
|
|
13970
13970
|
};
|
|
13971
13971
|
},
|
|
13972
|
-
{ annotations: {
|
|
13972
|
+
{ annotations: { readOnlyHint: true } }
|
|
13973
13973
|
),
|
|
13974
13974
|
tool(
|
|
13975
13975
|
"create_meeting",
|
|
@@ -14094,7 +14094,7 @@ function createMeetingTools(store) {
|
|
|
14094
14094
|
content: [{ type: "text", text: sections.join("\n") }]
|
|
14095
14095
|
};
|
|
14096
14096
|
},
|
|
14097
|
-
{ annotations: {
|
|
14097
|
+
{ annotations: { readOnlyHint: true } }
|
|
14098
14098
|
)
|
|
14099
14099
|
];
|
|
14100
14100
|
}
|
|
@@ -14111,9 +14111,17 @@ function collectGarMetrics(store) {
|
|
|
14111
14111
|
const blockedItems = allDocs.filter(
|
|
14112
14112
|
(d) => d.frontmatter.tags?.includes("blocked")
|
|
14113
14113
|
);
|
|
14114
|
-
const
|
|
14114
|
+
const tagOverdueItems = allDocs.filter(
|
|
14115
14115
|
(d) => d.frontmatter.tags?.includes("overdue")
|
|
14116
14116
|
);
|
|
14117
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
14118
|
+
const dateOverdueActions = openActions.filter((d) => {
|
|
14119
|
+
const dueDate = d.frontmatter.dueDate;
|
|
14120
|
+
return typeof dueDate === "string" && dueDate < today;
|
|
14121
|
+
});
|
|
14122
|
+
const overdueItems = [...tagOverdueItems, ...dateOverdueActions].filter(
|
|
14123
|
+
(d, i, arr) => arr.findIndex((x) => x.frontmatter.id === d.frontmatter.id) === i
|
|
14124
|
+
);
|
|
14117
14125
|
const openQuestions = store.list({ type: "question", status: "open" });
|
|
14118
14126
|
const riskItems = allDocs.filter(
|
|
14119
14127
|
(d) => d.frontmatter.tags?.includes("risk")
|
|
@@ -14222,6 +14230,253 @@ function evaluateGar(projectName, metrics) {
|
|
|
14222
14230
|
};
|
|
14223
14231
|
}
|
|
14224
14232
|
|
|
14233
|
+
// src/reports/health/collector.ts
|
|
14234
|
+
var FIELD_CHECKS = [
|
|
14235
|
+
{
|
|
14236
|
+
type: "action",
|
|
14237
|
+
openStatuses: ["open", "in-progress"],
|
|
14238
|
+
requiredFields: ["owner", "priority", "dueDate", "content"]
|
|
14239
|
+
},
|
|
14240
|
+
{
|
|
14241
|
+
type: "decision",
|
|
14242
|
+
openStatuses: ["open", "proposed"],
|
|
14243
|
+
requiredFields: ["owner", "content"]
|
|
14244
|
+
},
|
|
14245
|
+
{
|
|
14246
|
+
type: "question",
|
|
14247
|
+
openStatuses: ["open"],
|
|
14248
|
+
requiredFields: ["owner", "content"]
|
|
14249
|
+
},
|
|
14250
|
+
{
|
|
14251
|
+
type: "feature",
|
|
14252
|
+
openStatuses: ["draft", "approved"],
|
|
14253
|
+
requiredFields: ["owner", "priority", "content"]
|
|
14254
|
+
},
|
|
14255
|
+
{
|
|
14256
|
+
type: "epic",
|
|
14257
|
+
openStatuses: ["planned", "in-progress"],
|
|
14258
|
+
requiredFields: ["owner", "targetDate", "estimatedEffort", "content"]
|
|
14259
|
+
},
|
|
14260
|
+
{
|
|
14261
|
+
type: "sprint",
|
|
14262
|
+
openStatuses: ["planned", "active"],
|
|
14263
|
+
requiredFields: ["goal", "startDate", "endDate", "linkedEpics"]
|
|
14264
|
+
}
|
|
14265
|
+
];
|
|
14266
|
+
var STALE_THRESHOLD_DAYS = 14;
|
|
14267
|
+
var AGING_THRESHOLD_DAYS = 30;
|
|
14268
|
+
function daysBetween(a, b) {
|
|
14269
|
+
const msPerDay = 864e5;
|
|
14270
|
+
const dateA = new Date(a);
|
|
14271
|
+
const dateB = new Date(b);
|
|
14272
|
+
return Math.floor(Math.abs(dateB.getTime() - dateA.getTime()) / msPerDay);
|
|
14273
|
+
}
|
|
14274
|
+
function checkMissingFields(doc, requiredFields) {
|
|
14275
|
+
const missing = [];
|
|
14276
|
+
for (const field of requiredFields) {
|
|
14277
|
+
if (field === "content") {
|
|
14278
|
+
if (!doc.content || doc.content.trim().length === 0) {
|
|
14279
|
+
missing.push("content");
|
|
14280
|
+
}
|
|
14281
|
+
} else if (field === "linkedEpics") {
|
|
14282
|
+
const val = doc.frontmatter[field];
|
|
14283
|
+
if (!Array.isArray(val) || val.length === 0) {
|
|
14284
|
+
missing.push(field);
|
|
14285
|
+
}
|
|
14286
|
+
} else {
|
|
14287
|
+
const val = doc.frontmatter[field];
|
|
14288
|
+
if (val === void 0 || val === null || val === "") {
|
|
14289
|
+
missing.push(field);
|
|
14290
|
+
}
|
|
14291
|
+
}
|
|
14292
|
+
}
|
|
14293
|
+
return missing;
|
|
14294
|
+
}
|
|
14295
|
+
function collectCompleteness(store) {
|
|
14296
|
+
const result = {};
|
|
14297
|
+
for (const check2 of FIELD_CHECKS) {
|
|
14298
|
+
const allOfType = store.list({ type: check2.type });
|
|
14299
|
+
const openDocs = allOfType.filter(
|
|
14300
|
+
(d) => check2.openStatuses.includes(d.frontmatter.status)
|
|
14301
|
+
);
|
|
14302
|
+
const gaps = [];
|
|
14303
|
+
let complete = 0;
|
|
14304
|
+
for (const doc of openDocs) {
|
|
14305
|
+
const missingFields = checkMissingFields(doc, check2.requiredFields);
|
|
14306
|
+
if (missingFields.length === 0) {
|
|
14307
|
+
complete++;
|
|
14308
|
+
} else {
|
|
14309
|
+
gaps.push({
|
|
14310
|
+
id: doc.frontmatter.id,
|
|
14311
|
+
title: doc.frontmatter.title,
|
|
14312
|
+
missingFields
|
|
14313
|
+
});
|
|
14314
|
+
}
|
|
14315
|
+
}
|
|
14316
|
+
result[check2.type] = {
|
|
14317
|
+
total: openDocs.length,
|
|
14318
|
+
complete,
|
|
14319
|
+
gaps
|
|
14320
|
+
};
|
|
14321
|
+
}
|
|
14322
|
+
return result;
|
|
14323
|
+
}
|
|
14324
|
+
function collectProcess(store) {
|
|
14325
|
+
const today = (/* @__PURE__ */ new Date()).toISOString();
|
|
14326
|
+
const allDocs = store.list();
|
|
14327
|
+
const openStatuses = new Set(FIELD_CHECKS.flatMap((c) => c.openStatuses));
|
|
14328
|
+
const openDocs = allDocs.filter((d) => openStatuses.has(d.frontmatter.status));
|
|
14329
|
+
const stale = [];
|
|
14330
|
+
for (const doc of openDocs) {
|
|
14331
|
+
const updated = doc.frontmatter.updated ?? doc.frontmatter.created;
|
|
14332
|
+
const days = daysBetween(updated, today);
|
|
14333
|
+
if (days >= STALE_THRESHOLD_DAYS) {
|
|
14334
|
+
stale.push({ id: doc.frontmatter.id, title: doc.frontmatter.title, days });
|
|
14335
|
+
}
|
|
14336
|
+
}
|
|
14337
|
+
const openActions = store.list({ type: "action" }).filter((d) => d.frontmatter.status === "open" || d.frontmatter.status === "in-progress");
|
|
14338
|
+
const agingActions = [];
|
|
14339
|
+
for (const doc of openActions) {
|
|
14340
|
+
const days = daysBetween(doc.frontmatter.created, today);
|
|
14341
|
+
if (days >= AGING_THRESHOLD_DAYS) {
|
|
14342
|
+
agingActions.push({ id: doc.frontmatter.id, title: doc.frontmatter.title, days });
|
|
14343
|
+
}
|
|
14344
|
+
}
|
|
14345
|
+
const resolvedDecisions = store.list({ type: "decision" }).filter((d) => !["open", "proposed"].includes(d.frontmatter.status));
|
|
14346
|
+
let decisionTotal = 0;
|
|
14347
|
+
for (const doc of resolvedDecisions) {
|
|
14348
|
+
decisionTotal += daysBetween(doc.frontmatter.created, doc.frontmatter.updated);
|
|
14349
|
+
}
|
|
14350
|
+
const decisionVelocity = {
|
|
14351
|
+
avgDays: resolvedDecisions.length > 0 ? Math.round(decisionTotal / resolvedDecisions.length) : 0,
|
|
14352
|
+
count: resolvedDecisions.length
|
|
14353
|
+
};
|
|
14354
|
+
const answeredQuestions = store.list({ type: "question" }).filter((d) => d.frontmatter.status !== "open");
|
|
14355
|
+
let questionTotal = 0;
|
|
14356
|
+
for (const doc of answeredQuestions) {
|
|
14357
|
+
questionTotal += daysBetween(doc.frontmatter.created, doc.frontmatter.updated);
|
|
14358
|
+
}
|
|
14359
|
+
const questionResolution = {
|
|
14360
|
+
avgDays: answeredQuestions.length > 0 ? Math.round(questionTotal / answeredQuestions.length) : 0,
|
|
14361
|
+
count: answeredQuestions.length
|
|
14362
|
+
};
|
|
14363
|
+
return { stale, agingActions, decisionVelocity, questionResolution };
|
|
14364
|
+
}
|
|
14365
|
+
function collectHealthMetrics(store) {
|
|
14366
|
+
return {
|
|
14367
|
+
completeness: collectCompleteness(store),
|
|
14368
|
+
process: collectProcess(store)
|
|
14369
|
+
};
|
|
14370
|
+
}
|
|
14371
|
+
|
|
14372
|
+
// src/reports/health/evaluator.ts
|
|
14373
|
+
function worstStatus2(statuses) {
|
|
14374
|
+
if (statuses.includes("red")) return "red";
|
|
14375
|
+
if (statuses.includes("amber")) return "amber";
|
|
14376
|
+
return "green";
|
|
14377
|
+
}
|
|
14378
|
+
function completenessStatus(total, complete) {
|
|
14379
|
+
if (total === 0) return "green";
|
|
14380
|
+
const pct = Math.round(complete / total * 100);
|
|
14381
|
+
if (pct >= 100) return "green";
|
|
14382
|
+
if (pct >= 75) return "amber";
|
|
14383
|
+
return "red";
|
|
14384
|
+
}
|
|
14385
|
+
var TYPE_LABELS = {
|
|
14386
|
+
action: "Actions",
|
|
14387
|
+
decision: "Decisions",
|
|
14388
|
+
question: "Questions",
|
|
14389
|
+
feature: "Features",
|
|
14390
|
+
epic: "Epics",
|
|
14391
|
+
sprint: "Sprints"
|
|
14392
|
+
};
|
|
14393
|
+
function evaluateHealth(projectName, metrics) {
|
|
14394
|
+
const completeness = [];
|
|
14395
|
+
for (const [type, catMetrics] of Object.entries(metrics.completeness)) {
|
|
14396
|
+
const { total, complete, gaps } = catMetrics;
|
|
14397
|
+
const status = completenessStatus(total, complete);
|
|
14398
|
+
const pct = total > 0 ? Math.round(complete / total * 100) : 100;
|
|
14399
|
+
completeness.push({
|
|
14400
|
+
name: TYPE_LABELS[type] ?? type,
|
|
14401
|
+
status,
|
|
14402
|
+
summary: `${pct}% complete (${complete}/${total})`,
|
|
14403
|
+
items: gaps.map((g) => ({
|
|
14404
|
+
id: g.id,
|
|
14405
|
+
detail: `missing: ${g.missingFields.join(", ")}`
|
|
14406
|
+
}))
|
|
14407
|
+
});
|
|
14408
|
+
}
|
|
14409
|
+
const process3 = [];
|
|
14410
|
+
const staleCount = metrics.process.stale.length;
|
|
14411
|
+
const staleStatus = staleCount === 0 ? "green" : staleCount <= 3 ? "amber" : "red";
|
|
14412
|
+
process3.push({
|
|
14413
|
+
name: "Stale Items",
|
|
14414
|
+
status: staleStatus,
|
|
14415
|
+
summary: staleCount === 0 ? "no stale items" : `${staleCount} item(s) not updated in 14+ days`,
|
|
14416
|
+
items: metrics.process.stale.map((s) => ({
|
|
14417
|
+
id: s.id,
|
|
14418
|
+
detail: `${s.days} days since last update`
|
|
14419
|
+
}))
|
|
14420
|
+
});
|
|
14421
|
+
const agingCount = metrics.process.agingActions.length;
|
|
14422
|
+
const agingStatus = agingCount === 0 ? "green" : agingCount <= 3 ? "amber" : "red";
|
|
14423
|
+
process3.push({
|
|
14424
|
+
name: "Aging Actions",
|
|
14425
|
+
status: agingStatus,
|
|
14426
|
+
summary: agingCount === 0 ? "no aging actions" : `${agingCount} action(s) open for 30+ days`,
|
|
14427
|
+
items: metrics.process.agingActions.map((a) => ({
|
|
14428
|
+
id: a.id,
|
|
14429
|
+
detail: `open for ${a.days} days`
|
|
14430
|
+
}))
|
|
14431
|
+
});
|
|
14432
|
+
const dv = metrics.process.decisionVelocity;
|
|
14433
|
+
let dvStatus;
|
|
14434
|
+
if (dv.count === 0) {
|
|
14435
|
+
dvStatus = "green";
|
|
14436
|
+
} else if (dv.avgDays <= 7) {
|
|
14437
|
+
dvStatus = "green";
|
|
14438
|
+
} else if (dv.avgDays <= 21) {
|
|
14439
|
+
dvStatus = "amber";
|
|
14440
|
+
} else {
|
|
14441
|
+
dvStatus = "red";
|
|
14442
|
+
}
|
|
14443
|
+
process3.push({
|
|
14444
|
+
name: "Decision Velocity",
|
|
14445
|
+
status: dvStatus,
|
|
14446
|
+
summary: dv.count === 0 ? "no resolved decisions" : `avg ${dv.avgDays} days to resolve (${dv.count} decision(s))`,
|
|
14447
|
+
items: []
|
|
14448
|
+
});
|
|
14449
|
+
const qr = metrics.process.questionResolution;
|
|
14450
|
+
let qrStatus;
|
|
14451
|
+
if (qr.count === 0) {
|
|
14452
|
+
qrStatus = "green";
|
|
14453
|
+
} else if (qr.avgDays <= 7) {
|
|
14454
|
+
qrStatus = "green";
|
|
14455
|
+
} else if (qr.avgDays <= 14) {
|
|
14456
|
+
qrStatus = "amber";
|
|
14457
|
+
} else {
|
|
14458
|
+
qrStatus = "red";
|
|
14459
|
+
}
|
|
14460
|
+
process3.push({
|
|
14461
|
+
name: "Question Resolution",
|
|
14462
|
+
status: qrStatus,
|
|
14463
|
+
summary: qr.count === 0 ? "no answered questions" : `avg ${qr.avgDays} days to answer (${qr.count} question(s))`,
|
|
14464
|
+
items: []
|
|
14465
|
+
});
|
|
14466
|
+
const allStatuses = [
|
|
14467
|
+
...completeness.map((c) => c.status),
|
|
14468
|
+
...process3.map((p) => p.status)
|
|
14469
|
+
];
|
|
14470
|
+
const overall = worstStatus2(allStatuses);
|
|
14471
|
+
return {
|
|
14472
|
+
projectName,
|
|
14473
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
|
|
14474
|
+
overall,
|
|
14475
|
+
completeness,
|
|
14476
|
+
process: process3
|
|
14477
|
+
};
|
|
14478
|
+
}
|
|
14479
|
+
|
|
14225
14480
|
// src/plugins/builtin/tools/reports.ts
|
|
14226
14481
|
function createReportTools(store) {
|
|
14227
14482
|
return [
|
|
@@ -14241,7 +14496,8 @@ function createReportTools(store) {
|
|
|
14241
14496
|
id: d.frontmatter.id,
|
|
14242
14497
|
title: d.frontmatter.title,
|
|
14243
14498
|
owner: d.frontmatter.owner,
|
|
14244
|
-
priority: d.frontmatter.priority
|
|
14499
|
+
priority: d.frontmatter.priority,
|
|
14500
|
+
dueDate: d.frontmatter.dueDate
|
|
14245
14501
|
})),
|
|
14246
14502
|
completedActions: completedActions.map((d) => ({
|
|
14247
14503
|
id: d.frontmatter.id,
|
|
@@ -14260,7 +14516,7 @@ function createReportTools(store) {
|
|
|
14260
14516
|
content: [{ type: "text", text: JSON.stringify(report, null, 2) }]
|
|
14261
14517
|
};
|
|
14262
14518
|
},
|
|
14263
|
-
{ annotations: {
|
|
14519
|
+
{ annotations: { readOnlyHint: true } }
|
|
14264
14520
|
),
|
|
14265
14521
|
tool2(
|
|
14266
14522
|
"generate_risk_register",
|
|
@@ -14304,7 +14560,7 @@ function createReportTools(store) {
|
|
|
14304
14560
|
content: [{ type: "text", text: JSON.stringify(register, null, 2) }]
|
|
14305
14561
|
};
|
|
14306
14562
|
},
|
|
14307
|
-
{ annotations: {
|
|
14563
|
+
{ annotations: { readOnlyHint: true } }
|
|
14308
14564
|
),
|
|
14309
14565
|
tool2(
|
|
14310
14566
|
"generate_gar_report",
|
|
@@ -14317,7 +14573,7 @@ function createReportTools(store) {
|
|
|
14317
14573
|
content: [{ type: "text", text: JSON.stringify(report, null, 2) }]
|
|
14318
14574
|
};
|
|
14319
14575
|
},
|
|
14320
|
-
{ annotations: {
|
|
14576
|
+
{ annotations: { readOnlyHint: true } }
|
|
14321
14577
|
),
|
|
14322
14578
|
tool2(
|
|
14323
14579
|
"generate_epic_progress",
|
|
@@ -14402,7 +14658,7 @@ function createReportTools(store) {
|
|
|
14402
14658
|
]
|
|
14403
14659
|
};
|
|
14404
14660
|
},
|
|
14405
|
-
{ annotations: {
|
|
14661
|
+
{ annotations: { readOnlyHint: true } }
|
|
14406
14662
|
),
|
|
14407
14663
|
tool2(
|
|
14408
14664
|
"generate_sprint_progress",
|
|
@@ -14447,7 +14703,8 @@ function createReportTools(store) {
|
|
|
14447
14703
|
id: d.frontmatter.id,
|
|
14448
14704
|
title: d.frontmatter.title,
|
|
14449
14705
|
type: d.frontmatter.type,
|
|
14450
|
-
status: d.frontmatter.status
|
|
14706
|
+
status: d.frontmatter.status,
|
|
14707
|
+
dueDate: d.frontmatter.dueDate
|
|
14451
14708
|
}))
|
|
14452
14709
|
}
|
|
14453
14710
|
};
|
|
@@ -14456,7 +14713,7 @@ function createReportTools(store) {
|
|
|
14456
14713
|
content: [{ type: "text", text: JSON.stringify({ sprints }, null, 2) }]
|
|
14457
14714
|
};
|
|
14458
14715
|
},
|
|
14459
|
-
{ annotations: {
|
|
14716
|
+
{ annotations: { readOnlyHint: true } }
|
|
14460
14717
|
),
|
|
14461
14718
|
tool2(
|
|
14462
14719
|
"generate_feature_progress",
|
|
@@ -14496,7 +14753,20 @@ function createReportTools(store) {
|
|
|
14496
14753
|
content: [{ type: "text", text: JSON.stringify({ features }, null, 2) }]
|
|
14497
14754
|
};
|
|
14498
14755
|
},
|
|
14499
|
-
{ annotations: {
|
|
14756
|
+
{ annotations: { readOnlyHint: true } }
|
|
14757
|
+
),
|
|
14758
|
+
tool2(
|
|
14759
|
+
"generate_health_report",
|
|
14760
|
+
"Generate a governance health check report covering artifact completeness and process health metrics",
|
|
14761
|
+
{},
|
|
14762
|
+
async () => {
|
|
14763
|
+
const metrics = collectHealthMetrics(store);
|
|
14764
|
+
const report = evaluateHealth("project", metrics);
|
|
14765
|
+
return {
|
|
14766
|
+
content: [{ type: "text", text: JSON.stringify(report, null, 2) }]
|
|
14767
|
+
};
|
|
14768
|
+
},
|
|
14769
|
+
{ annotations: { readOnlyHint: true } }
|
|
14500
14770
|
),
|
|
14501
14771
|
tool2(
|
|
14502
14772
|
"save_report",
|
|
@@ -14558,7 +14828,7 @@ function createFeatureTools(store) {
|
|
|
14558
14828
|
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
14559
14829
|
};
|
|
14560
14830
|
},
|
|
14561
|
-
{ annotations: {
|
|
14831
|
+
{ annotations: { readOnlyHint: true } }
|
|
14562
14832
|
),
|
|
14563
14833
|
tool3(
|
|
14564
14834
|
"get_feature",
|
|
@@ -14585,7 +14855,7 @@ function createFeatureTools(store) {
|
|
|
14585
14855
|
]
|
|
14586
14856
|
};
|
|
14587
14857
|
},
|
|
14588
|
-
{ annotations: {
|
|
14858
|
+
{ annotations: { readOnlyHint: true } }
|
|
14589
14859
|
),
|
|
14590
14860
|
tool3(
|
|
14591
14861
|
"create_feature",
|
|
@@ -14676,7 +14946,7 @@ function createEpicTools(store) {
|
|
|
14676
14946
|
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
14677
14947
|
};
|
|
14678
14948
|
},
|
|
14679
|
-
{ annotations: {
|
|
14949
|
+
{ annotations: { readOnlyHint: true } }
|
|
14680
14950
|
),
|
|
14681
14951
|
tool4(
|
|
14682
14952
|
"get_epic",
|
|
@@ -14703,7 +14973,7 @@ function createEpicTools(store) {
|
|
|
14703
14973
|
]
|
|
14704
14974
|
};
|
|
14705
14975
|
},
|
|
14706
|
-
{ annotations: {
|
|
14976
|
+
{ annotations: { readOnlyHint: true } }
|
|
14707
14977
|
),
|
|
14708
14978
|
tool4(
|
|
14709
14979
|
"create_epic",
|
|
@@ -14835,7 +15105,7 @@ function createContributionTools(store) {
|
|
|
14835
15105
|
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
14836
15106
|
};
|
|
14837
15107
|
},
|
|
14838
|
-
{ annotations: {
|
|
15108
|
+
{ annotations: { readOnlyHint: true } }
|
|
14839
15109
|
),
|
|
14840
15110
|
tool5(
|
|
14841
15111
|
"get_contribution",
|
|
@@ -14862,7 +15132,7 @@ function createContributionTools(store) {
|
|
|
14862
15132
|
]
|
|
14863
15133
|
};
|
|
14864
15134
|
},
|
|
14865
|
-
{ annotations: {
|
|
15135
|
+
{ annotations: { readOnlyHint: true } }
|
|
14866
15136
|
),
|
|
14867
15137
|
tool5(
|
|
14868
15138
|
"create_contribution",
|
|
@@ -14947,7 +15217,7 @@ function createSprintTools(store) {
|
|
|
14947
15217
|
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
14948
15218
|
};
|
|
14949
15219
|
},
|
|
14950
|
-
{ annotations: {
|
|
15220
|
+
{ annotations: { readOnlyHint: true } }
|
|
14951
15221
|
),
|
|
14952
15222
|
tool6(
|
|
14953
15223
|
"get_sprint",
|
|
@@ -14974,7 +15244,7 @@ function createSprintTools(store) {
|
|
|
14974
15244
|
]
|
|
14975
15245
|
};
|
|
14976
15246
|
},
|
|
14977
|
-
{ annotations: {
|
|
15247
|
+
{ annotations: { readOnlyHint: true } }
|
|
14978
15248
|
),
|
|
14979
15249
|
tool6(
|
|
14980
15250
|
"create_sprint",
|
|
@@ -15271,7 +15541,7 @@ function createSprintPlanningTools(store) {
|
|
|
15271
15541
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
15272
15542
|
};
|
|
15273
15543
|
},
|
|
15274
|
-
{ annotations: {
|
|
15544
|
+
{ annotations: { readOnlyHint: true } }
|
|
15275
15545
|
)
|
|
15276
15546
|
];
|
|
15277
15547
|
}
|
|
@@ -15325,6 +15595,7 @@ var genericAgilePlugin = {
|
|
|
15325
15595
|
- Do NOT create epics \u2014 that is the Tech Lead's responsibility. You can view epics to track progress.
|
|
15326
15596
|
- Use priority levels (critical, high, medium, low) to communicate business value.
|
|
15327
15597
|
- Tag features for categorization and cross-referencing.
|
|
15598
|
+
- Include a \`dueDate\` on actions when target dates are known, to enable schedule tracking and overdue detection.
|
|
15328
15599
|
|
|
15329
15600
|
**Contribution Tools:**
|
|
15330
15601
|
- **list_contributions** / **get_contribution**: Browse and read contribution records.
|
|
@@ -15355,6 +15626,7 @@ var genericAgilePlugin = {
|
|
|
15355
15626
|
- Tag work items (actions, decisions, questions) with \`epic:E-xxx\` to group them under an epic.
|
|
15356
15627
|
- Collaborate with the Delivery Manager on target dates and effort estimates.
|
|
15357
15628
|
- Each epic should have a clear scope and definition of done.
|
|
15629
|
+
- Set \`dueDate\` on technical actions based on sprint timelines or epic target dates. Use the \`sprints\` parameter to assign actions to relevant sprints.
|
|
15358
15630
|
|
|
15359
15631
|
**Contribution Tools:**
|
|
15360
15632
|
- **list_contributions** / **get_contribution**: Browse and read contribution records.
|
|
@@ -15414,6 +15686,11 @@ var genericAgilePlugin = {
|
|
|
15414
15686
|
- **generate_sprint_progress**: Progress report for a specific sprint or all sprints \u2014 shows linked epics with statuses, work items tagged \`sprint:SP-xxx\` grouped by status, and done/total completion %.
|
|
15415
15687
|
- Use \`save_report\` with reportType "sprint-progress" to persist sprint reports.
|
|
15416
15688
|
|
|
15689
|
+
**Date Enforcement:**
|
|
15690
|
+
- Always set \`dueDate\` when creating or updating actions. Use the \`sprints\` parameter to assign actions to sprints \u2014 the tool translates this into \`sprint:SP-xxx\` tags automatically.
|
|
15691
|
+
- When create_action suggests matching sprints in its response, review and assign accordingly using update_action.
|
|
15692
|
+
- Use \`suggest_sprints_for_action\` to find the right sprint for existing actions that lack sprint assignment.
|
|
15693
|
+
|
|
15417
15694
|
**Sprint Workflow:**
|
|
15418
15695
|
- Create sprints with clear goals and date boundaries.
|
|
15419
15696
|
- Assign epics to sprints via linkedEpics.
|
|
@@ -15433,7 +15710,7 @@ var genericAgilePlugin = {
|
|
|
15433
15710
|
**Sprints** (SP-xxx): Time-boxed iterations that group epics and work items with delivery dates. Sprints progress through planned \u2192 active \u2192 completed (or cancelled).
|
|
15434
15711
|
**Meetings**: Meeting records with attendees, agendas, and notes.
|
|
15435
15712
|
|
|
15436
|
-
**Key workflow rule:** Epics must link to approved features \u2014 the system enforces this. The Product Owner defines and approves features, the Tech Lead breaks them into epics, the Delivery Manager plans sprints and tracks dates and progress. Work items are associated with sprints via \`sprint:SP-xxx\` tags.
|
|
15713
|
+
**Key workflow rule:** Epics must link to approved features \u2014 the system enforces this. The Product Owner defines and approves features, the Tech Lead breaks them into epics, the Delivery Manager plans sprints and tracks dates and progress. Work items are associated with sprints via \`sprint:SP-xxx\` tags. Actions support a \`dueDate\` field for schedule tracking \u2014 actions with a past due date are automatically flagged as overdue in GAR reports. Use the \`sprints\` parameter on create_action/update_action to assign actions to sprints.
|
|
15437
15714
|
|
|
15438
15715
|
- **list_meetings** / **get_meeting**: Browse and read meeting records.
|
|
15439
15716
|
- **create_meeting**: Record meetings with attendees, date, and agenda. The meeting date is required \u2014 extract it from the meeting content or ask the user if not found.
|
|
@@ -15479,7 +15756,7 @@ function createUseCaseTools(store) {
|
|
|
15479
15756
|
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
15480
15757
|
};
|
|
15481
15758
|
},
|
|
15482
|
-
{ annotations: {
|
|
15759
|
+
{ annotations: { readOnlyHint: true } }
|
|
15483
15760
|
),
|
|
15484
15761
|
tool8(
|
|
15485
15762
|
"get_use_case",
|
|
@@ -15506,7 +15783,7 @@ function createUseCaseTools(store) {
|
|
|
15506
15783
|
]
|
|
15507
15784
|
};
|
|
15508
15785
|
},
|
|
15509
|
-
{ annotations: {
|
|
15786
|
+
{ annotations: { readOnlyHint: true } }
|
|
15510
15787
|
),
|
|
15511
15788
|
tool8(
|
|
15512
15789
|
"create_use_case",
|
|
@@ -15604,7 +15881,7 @@ function createTechAssessmentTools(store) {
|
|
|
15604
15881
|
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
15605
15882
|
};
|
|
15606
15883
|
},
|
|
15607
|
-
{ annotations: {
|
|
15884
|
+
{ annotations: { readOnlyHint: true } }
|
|
15608
15885
|
),
|
|
15609
15886
|
tool9(
|
|
15610
15887
|
"get_tech_assessment",
|
|
@@ -15631,7 +15908,7 @@ function createTechAssessmentTools(store) {
|
|
|
15631
15908
|
]
|
|
15632
15909
|
};
|
|
15633
15910
|
},
|
|
15634
|
-
{ annotations: {
|
|
15911
|
+
{ annotations: { readOnlyHint: true } }
|
|
15635
15912
|
),
|
|
15636
15913
|
tool9(
|
|
15637
15914
|
"create_tech_assessment",
|
|
@@ -15765,7 +16042,7 @@ function createExtensionDesignTools(store) {
|
|
|
15765
16042
|
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
15766
16043
|
};
|
|
15767
16044
|
},
|
|
15768
|
-
{ annotations: {
|
|
16045
|
+
{ annotations: { readOnlyHint: true } }
|
|
15769
16046
|
),
|
|
15770
16047
|
tool10(
|
|
15771
16048
|
"get_extension_design",
|
|
@@ -15792,7 +16069,7 @@ function createExtensionDesignTools(store) {
|
|
|
15792
16069
|
]
|
|
15793
16070
|
};
|
|
15794
16071
|
},
|
|
15795
|
-
{ annotations: {
|
|
16072
|
+
{ annotations: { readOnlyHint: true } }
|
|
15796
16073
|
),
|
|
15797
16074
|
tool10(
|
|
15798
16075
|
"create_extension_design",
|
|
@@ -15944,7 +16221,7 @@ function createAemReportTools(store) {
|
|
|
15944
16221
|
]
|
|
15945
16222
|
};
|
|
15946
16223
|
},
|
|
15947
|
-
{ annotations: {
|
|
16224
|
+
{ annotations: { readOnlyHint: true } }
|
|
15948
16225
|
),
|
|
15949
16226
|
tool11(
|
|
15950
16227
|
"generate_tech_readiness",
|
|
@@ -15996,7 +16273,7 @@ function createAemReportTools(store) {
|
|
|
15996
16273
|
]
|
|
15997
16274
|
};
|
|
15998
16275
|
},
|
|
15999
|
-
{ annotations: {
|
|
16276
|
+
{ annotations: { readOnlyHint: true } }
|
|
16000
16277
|
),
|
|
16001
16278
|
tool11(
|
|
16002
16279
|
"generate_phase_status",
|
|
@@ -16051,7 +16328,7 @@ function createAemReportTools(store) {
|
|
|
16051
16328
|
]
|
|
16052
16329
|
};
|
|
16053
16330
|
},
|
|
16054
|
-
{ annotations: {
|
|
16331
|
+
{ annotations: { readOnlyHint: true } }
|
|
16055
16332
|
)
|
|
16056
16333
|
];
|
|
16057
16334
|
}
|
|
@@ -16083,7 +16360,7 @@ function createAemPhaseTools(store, marvinDir) {
|
|
|
16083
16360
|
]
|
|
16084
16361
|
};
|
|
16085
16362
|
},
|
|
16086
|
-
{ annotations: {
|
|
16363
|
+
{ annotations: { readOnlyHint: true } }
|
|
16087
16364
|
),
|
|
16088
16365
|
tool12(
|
|
16089
16366
|
"advance_phase",
|
|
@@ -16517,6 +16794,8 @@ var deliveryManager = {
|
|
|
16517
16794
|
|
|
16518
16795
|
## How You Work
|
|
16519
16796
|
- Review open actions (A-xxx) and follow up on overdue items
|
|
16797
|
+
- Ensure every action has a dueDate \u2014 use update_action to backfill existing ones
|
|
16798
|
+
- Assign actions to sprints when sprint planning is active, using the sprints parameter
|
|
16520
16799
|
- Ensure decisions (D-xxx) are properly documented with rationale
|
|
16521
16800
|
- Track questions (Q-xxx) and ensure they get answered
|
|
16522
16801
|
- Monitor project health and flag risks early
|
|
@@ -16631,7 +16910,7 @@ function buildSystemPrompt(persona, projectConfig, pluginPromptFragment, skillPr
|
|
|
16631
16910
|
## Available Tools
|
|
16632
16911
|
You have access to governance tools for managing project artifacts:
|
|
16633
16912
|
- **Decisions** (D-xxx): List, get, create, and update decisions
|
|
16634
|
-
- **Actions** (A-xxx): List, get, create, and update action items
|
|
16913
|
+
- **Actions** (A-xxx): List, get, create, and update action items. Actions support \`dueDate\` for schedule tracking and \`sprints\` for sprint assignment.
|
|
16635
16914
|
- **Questions** (Q-xxx): List, get, create, and update questions
|
|
16636
16915
|
- **Features** (F-xxx): List, get, create, and update feature definitions
|
|
16637
16916
|
- **Epics** (E-xxx): List, get, create, and update implementation epics (must link to approved features)
|
|
@@ -16982,7 +17261,7 @@ function createDecisionTools(store) {
|
|
|
16982
17261
|
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
16983
17262
|
};
|
|
16984
17263
|
},
|
|
16985
|
-
{ annotations: {
|
|
17264
|
+
{ annotations: { readOnlyHint: true } }
|
|
16986
17265
|
),
|
|
16987
17266
|
tool13(
|
|
16988
17267
|
"get_decision",
|
|
@@ -17009,7 +17288,7 @@ function createDecisionTools(store) {
|
|
|
17009
17288
|
]
|
|
17010
17289
|
};
|
|
17011
17290
|
},
|
|
17012
|
-
{ annotations: {
|
|
17291
|
+
{ annotations: { readOnlyHint: true } }
|
|
17013
17292
|
),
|
|
17014
17293
|
tool13(
|
|
17015
17294
|
"create_decision",
|
|
@@ -17070,6 +17349,19 @@ function createDecisionTools(store) {
|
|
|
17070
17349
|
|
|
17071
17350
|
// src/agent/tools/actions.ts
|
|
17072
17351
|
import { tool as tool14 } from "@anthropic-ai/claude-agent-sdk";
|
|
17352
|
+
function findMatchingSprints(store, dueDate) {
|
|
17353
|
+
const sprints = store.list({ type: "sprint" });
|
|
17354
|
+
return sprints.filter((s) => {
|
|
17355
|
+
const start = s.frontmatter.startDate;
|
|
17356
|
+
const end = s.frontmatter.endDate;
|
|
17357
|
+
return start && end && dueDate >= start && dueDate <= end;
|
|
17358
|
+
}).map((s) => ({
|
|
17359
|
+
id: s.frontmatter.id,
|
|
17360
|
+
title: s.frontmatter.title,
|
|
17361
|
+
startDate: s.frontmatter.startDate,
|
|
17362
|
+
endDate: s.frontmatter.endDate
|
|
17363
|
+
}));
|
|
17364
|
+
}
|
|
17073
17365
|
function createActionTools(store) {
|
|
17074
17366
|
return [
|
|
17075
17367
|
tool14(
|
|
@@ -17085,19 +17377,24 @@ function createActionTools(store) {
|
|
|
17085
17377
|
status: args.status,
|
|
17086
17378
|
owner: args.owner
|
|
17087
17379
|
});
|
|
17088
|
-
const summary = docs.map((d) =>
|
|
17089
|
-
|
|
17090
|
-
|
|
17091
|
-
|
|
17092
|
-
|
|
17093
|
-
|
|
17094
|
-
|
|
17095
|
-
|
|
17380
|
+
const summary = docs.map((d) => {
|
|
17381
|
+
const sprintIds = (d.frontmatter.tags ?? []).filter((t) => t.startsWith("sprint:")).map((t) => t.slice(7));
|
|
17382
|
+
return {
|
|
17383
|
+
id: d.frontmatter.id,
|
|
17384
|
+
title: d.frontmatter.title,
|
|
17385
|
+
status: d.frontmatter.status,
|
|
17386
|
+
owner: d.frontmatter.owner,
|
|
17387
|
+
priority: d.frontmatter.priority,
|
|
17388
|
+
dueDate: d.frontmatter.dueDate,
|
|
17389
|
+
sprints: sprintIds.length > 0 ? sprintIds : void 0,
|
|
17390
|
+
created: d.frontmatter.created
|
|
17391
|
+
};
|
|
17392
|
+
});
|
|
17096
17393
|
return {
|
|
17097
17394
|
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
17098
17395
|
};
|
|
17099
17396
|
},
|
|
17100
|
-
{ annotations: {
|
|
17397
|
+
{ annotations: { readOnlyHint: true } }
|
|
17101
17398
|
),
|
|
17102
17399
|
tool14(
|
|
17103
17400
|
"get_action",
|
|
@@ -17124,7 +17421,7 @@ function createActionTools(store) {
|
|
|
17124
17421
|
]
|
|
17125
17422
|
};
|
|
17126
17423
|
},
|
|
17127
|
-
{ annotations: {
|
|
17424
|
+
{ annotations: { readOnlyHint: true } }
|
|
17128
17425
|
),
|
|
17129
17426
|
tool14(
|
|
17130
17427
|
"create_action",
|
|
@@ -17135,9 +17432,18 @@ function createActionTools(store) {
|
|
|
17135
17432
|
status: external_exports.string().optional().describe("Status (default: 'open')"),
|
|
17136
17433
|
owner: external_exports.string().optional().describe("Person responsible"),
|
|
17137
17434
|
priority: external_exports.string().optional().describe("Priority (high, medium, low)"),
|
|
17138
|
-
tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization")
|
|
17435
|
+
tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization"),
|
|
17436
|
+
dueDate: external_exports.string().optional().describe("Due date in ISO format (e.g. '2026-03-15')"),
|
|
17437
|
+
sprints: external_exports.array(external_exports.string()).optional().describe("Sprint IDs to assign (e.g. ['SP-001']). Adds sprint:SP-xxx tags.")
|
|
17139
17438
|
},
|
|
17140
17439
|
async (args) => {
|
|
17440
|
+
const tags = [...args.tags ?? []];
|
|
17441
|
+
if (args.sprints) {
|
|
17442
|
+
for (const sprintId of args.sprints) {
|
|
17443
|
+
const tag = `sprint:${sprintId}`;
|
|
17444
|
+
if (!tags.includes(tag)) tags.push(tag);
|
|
17445
|
+
}
|
|
17446
|
+
}
|
|
17141
17447
|
const doc = store.create(
|
|
17142
17448
|
"action",
|
|
17143
17449
|
{
|
|
@@ -17145,17 +17451,21 @@ function createActionTools(store) {
|
|
|
17145
17451
|
status: args.status,
|
|
17146
17452
|
owner: args.owner,
|
|
17147
17453
|
priority: args.priority,
|
|
17148
|
-
tags:
|
|
17454
|
+
tags: tags.length > 0 ? tags : void 0,
|
|
17455
|
+
dueDate: args.dueDate
|
|
17149
17456
|
},
|
|
17150
17457
|
args.content
|
|
17151
17458
|
);
|
|
17459
|
+
const parts = [`Created action ${doc.frontmatter.id}: ${doc.frontmatter.title}`];
|
|
17460
|
+
if (args.dueDate && (!args.sprints || args.sprints.length === 0)) {
|
|
17461
|
+
const matching = findMatchingSprints(store, args.dueDate);
|
|
17462
|
+
if (matching.length > 0) {
|
|
17463
|
+
const suggestions = matching.map((s) => `${s.id} "${s.title}" (${s.startDate} \u2013 ${s.endDate})`).join(", ");
|
|
17464
|
+
parts.push(`Suggested sprints for dueDate ${args.dueDate}: ${suggestions}. Use the sprints parameter or update_action to assign.`);
|
|
17465
|
+
}
|
|
17466
|
+
}
|
|
17152
17467
|
return {
|
|
17153
|
-
content: [
|
|
17154
|
-
{
|
|
17155
|
-
type: "text",
|
|
17156
|
-
text: `Created action ${doc.frontmatter.id}: ${doc.frontmatter.title}`
|
|
17157
|
-
}
|
|
17158
|
-
]
|
|
17468
|
+
content: [{ type: "text", text: parts.join("\n") }]
|
|
17159
17469
|
};
|
|
17160
17470
|
}
|
|
17161
17471
|
),
|
|
@@ -17168,10 +17478,25 @@ function createActionTools(store) {
|
|
|
17168
17478
|
status: external_exports.string().optional().describe("New status"),
|
|
17169
17479
|
content: external_exports.string().optional().describe("New content"),
|
|
17170
17480
|
owner: external_exports.string().optional().describe("New owner"),
|
|
17171
|
-
priority: external_exports.string().optional().describe("New priority")
|
|
17481
|
+
priority: external_exports.string().optional().describe("New priority"),
|
|
17482
|
+
dueDate: external_exports.string().optional().describe("Due date in ISO format (e.g. '2026-03-15')"),
|
|
17483
|
+
sprints: external_exports.array(external_exports.string()).optional().describe("Sprint IDs to assign (replaces existing sprint tags). E.g. ['SP-001'].")
|
|
17172
17484
|
},
|
|
17173
17485
|
async (args) => {
|
|
17174
|
-
const { id, content, ...updates } = args;
|
|
17486
|
+
const { id, content, sprints, ...updates } = args;
|
|
17487
|
+
if (sprints !== void 0) {
|
|
17488
|
+
const existing = store.get(id);
|
|
17489
|
+
if (!existing) {
|
|
17490
|
+
return {
|
|
17491
|
+
content: [{ type: "text", text: `Action ${id} not found` }],
|
|
17492
|
+
isError: true
|
|
17493
|
+
};
|
|
17494
|
+
}
|
|
17495
|
+
const existingTags = existing.frontmatter.tags ?? [];
|
|
17496
|
+
const nonSprintTags = existingTags.filter((t) => !t.startsWith("sprint:"));
|
|
17497
|
+
const newSprintTags = sprints.map((s) => `sprint:${s}`);
|
|
17498
|
+
updates.tags = [...nonSprintTags, ...newSprintTags];
|
|
17499
|
+
}
|
|
17175
17500
|
const doc = store.update(id, updates, content);
|
|
17176
17501
|
return {
|
|
17177
17502
|
content: [
|
|
@@ -17182,6 +17507,35 @@ function createActionTools(store) {
|
|
|
17182
17507
|
]
|
|
17183
17508
|
};
|
|
17184
17509
|
}
|
|
17510
|
+
),
|
|
17511
|
+
tool14(
|
|
17512
|
+
"suggest_sprints_for_action",
|
|
17513
|
+
"Suggest sprints whose date range contains the given due date. Helps assign actions to the right sprint.",
|
|
17514
|
+
{
|
|
17515
|
+
dueDate: external_exports.string().describe("Due date in ISO format (e.g. '2026-03-15')")
|
|
17516
|
+
},
|
|
17517
|
+
async (args) => {
|
|
17518
|
+
const matching = findMatchingSprints(store, args.dueDate);
|
|
17519
|
+
if (matching.length === 0) {
|
|
17520
|
+
return {
|
|
17521
|
+
content: [
|
|
17522
|
+
{
|
|
17523
|
+
type: "text",
|
|
17524
|
+
text: `No sprints found containing dueDate ${args.dueDate}.`
|
|
17525
|
+
}
|
|
17526
|
+
]
|
|
17527
|
+
};
|
|
17528
|
+
}
|
|
17529
|
+
return {
|
|
17530
|
+
content: [
|
|
17531
|
+
{
|
|
17532
|
+
type: "text",
|
|
17533
|
+
text: JSON.stringify(matching, null, 2)
|
|
17534
|
+
}
|
|
17535
|
+
]
|
|
17536
|
+
};
|
|
17537
|
+
},
|
|
17538
|
+
{ annotations: { readOnlyHint: true } }
|
|
17185
17539
|
)
|
|
17186
17540
|
];
|
|
17187
17541
|
}
|
|
@@ -17209,7 +17563,7 @@ function createQuestionTools(store) {
|
|
|
17209
17563
|
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
17210
17564
|
};
|
|
17211
17565
|
},
|
|
17212
|
-
{ annotations: {
|
|
17566
|
+
{ annotations: { readOnlyHint: true } }
|
|
17213
17567
|
),
|
|
17214
17568
|
tool15(
|
|
17215
17569
|
"get_question",
|
|
@@ -17236,7 +17590,7 @@ function createQuestionTools(store) {
|
|
|
17236
17590
|
]
|
|
17237
17591
|
};
|
|
17238
17592
|
},
|
|
17239
|
-
{ annotations: {
|
|
17593
|
+
{ annotations: { readOnlyHint: true } }
|
|
17240
17594
|
),
|
|
17241
17595
|
tool15(
|
|
17242
17596
|
"create_question",
|
|
@@ -17329,7 +17683,7 @@ function createDocumentTools(store) {
|
|
|
17329
17683
|
]
|
|
17330
17684
|
};
|
|
17331
17685
|
},
|
|
17332
|
-
{ annotations: {
|
|
17686
|
+
{ annotations: { readOnlyHint: true } }
|
|
17333
17687
|
),
|
|
17334
17688
|
tool16(
|
|
17335
17689
|
"read_document",
|
|
@@ -17356,7 +17710,7 @@ function createDocumentTools(store) {
|
|
|
17356
17710
|
]
|
|
17357
17711
|
};
|
|
17358
17712
|
},
|
|
17359
|
-
{ annotations: {
|
|
17713
|
+
{ annotations: { readOnlyHint: true } }
|
|
17360
17714
|
),
|
|
17361
17715
|
tool16(
|
|
17362
17716
|
"project_summary",
|
|
@@ -17384,7 +17738,7 @@ function createDocumentTools(store) {
|
|
|
17384
17738
|
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
17385
17739
|
};
|
|
17386
17740
|
},
|
|
17387
|
-
{ annotations: {
|
|
17741
|
+
{ annotations: { readOnlyHint: true } }
|
|
17388
17742
|
)
|
|
17389
17743
|
];
|
|
17390
17744
|
}
|
|
@@ -17421,7 +17775,7 @@ function createSourceTools(manifest) {
|
|
|
17421
17775
|
]
|
|
17422
17776
|
};
|
|
17423
17777
|
},
|
|
17424
|
-
{ annotations: {
|
|
17778
|
+
{ annotations: { readOnlyHint: true } }
|
|
17425
17779
|
),
|
|
17426
17780
|
tool17(
|
|
17427
17781
|
"get_source_info",
|
|
@@ -17455,7 +17809,7 @@ function createSourceTools(manifest) {
|
|
|
17455
17809
|
]
|
|
17456
17810
|
};
|
|
17457
17811
|
},
|
|
17458
|
-
{ annotations: {
|
|
17812
|
+
{ annotations: { readOnlyHint: true } }
|
|
17459
17813
|
)
|
|
17460
17814
|
];
|
|
17461
17815
|
}
|
|
@@ -17486,7 +17840,7 @@ function createSessionTools(store) {
|
|
|
17486
17840
|
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
17487
17841
|
};
|
|
17488
17842
|
},
|
|
17489
|
-
{ annotations: {
|
|
17843
|
+
{ annotations: { readOnlyHint: true } }
|
|
17490
17844
|
),
|
|
17491
17845
|
tool18(
|
|
17492
17846
|
"get_session",
|
|
@@ -17504,7 +17858,7 @@ function createSessionTools(store) {
|
|
|
17504
17858
|
content: [{ type: "text", text: JSON.stringify(session, null, 2) }]
|
|
17505
17859
|
};
|
|
17506
17860
|
},
|
|
17507
|
-
{ annotations: {
|
|
17861
|
+
{ annotations: { readOnlyHint: true } }
|
|
17508
17862
|
)
|
|
17509
17863
|
];
|
|
17510
17864
|
}
|
|
@@ -17584,6 +17938,46 @@ function getBoardData(store, type) {
|
|
|
17584
17938
|
}));
|
|
17585
17939
|
return { columns, type, types };
|
|
17586
17940
|
}
|
|
17941
|
+
function getDiagramData(store) {
|
|
17942
|
+
const allDocs = store.list();
|
|
17943
|
+
const sprints = [];
|
|
17944
|
+
const epics = [];
|
|
17945
|
+
const features = [];
|
|
17946
|
+
const statusCounts = {};
|
|
17947
|
+
for (const doc of allDocs) {
|
|
17948
|
+
const fm = doc.frontmatter;
|
|
17949
|
+
const status = fm.status.toLowerCase();
|
|
17950
|
+
statusCounts[status] = (statusCounts[status] ?? 0) + 1;
|
|
17951
|
+
switch (fm.type) {
|
|
17952
|
+
case "sprint":
|
|
17953
|
+
sprints.push({
|
|
17954
|
+
id: fm.id,
|
|
17955
|
+
title: fm.title,
|
|
17956
|
+
status: fm.status,
|
|
17957
|
+
startDate: fm.startDate,
|
|
17958
|
+
endDate: fm.endDate,
|
|
17959
|
+
linkedEpics: fm.linkedEpics ?? []
|
|
17960
|
+
});
|
|
17961
|
+
break;
|
|
17962
|
+
case "epic":
|
|
17963
|
+
epics.push({
|
|
17964
|
+
id: fm.id,
|
|
17965
|
+
title: fm.title,
|
|
17966
|
+
status: fm.status,
|
|
17967
|
+
linkedFeature: fm.linkedFeature
|
|
17968
|
+
});
|
|
17969
|
+
break;
|
|
17970
|
+
case "feature":
|
|
17971
|
+
features.push({
|
|
17972
|
+
id: fm.id,
|
|
17973
|
+
title: fm.title,
|
|
17974
|
+
status: fm.status
|
|
17975
|
+
});
|
|
17976
|
+
break;
|
|
17977
|
+
}
|
|
17978
|
+
}
|
|
17979
|
+
return { sprints, epics, features, statusCounts };
|
|
17980
|
+
}
|
|
17587
17981
|
|
|
17588
17982
|
// src/web/templates/layout.ts
|
|
17589
17983
|
function escapeHtml(str) {
|
|
@@ -17614,16 +18008,43 @@ function renderMarkdown(md) {
|
|
|
17614
18008
|
const out = [];
|
|
17615
18009
|
let inList = false;
|
|
17616
18010
|
let listTag = "ul";
|
|
17617
|
-
|
|
17618
|
-
|
|
18011
|
+
let inTable = false;
|
|
18012
|
+
let i = 0;
|
|
18013
|
+
while (i < lines.length) {
|
|
18014
|
+
const line = lines[i];
|
|
17619
18015
|
if (inList && !/^\s*[-*]\s/.test(line) && !/^\s*\d+\.\s/.test(line) && line.trim() !== "") {
|
|
17620
18016
|
out.push(`</${listTag}>`);
|
|
17621
18017
|
inList = false;
|
|
17622
18018
|
}
|
|
18019
|
+
if (inTable && !/^\s*\|/.test(line)) {
|
|
18020
|
+
out.push("</tbody></table></div>");
|
|
18021
|
+
inTable = false;
|
|
18022
|
+
}
|
|
18023
|
+
if (/^(-{3,}|\*{3,}|_{3,})\s*$/.test(line.trim())) {
|
|
18024
|
+
i++;
|
|
18025
|
+
out.push("<hr>");
|
|
18026
|
+
continue;
|
|
18027
|
+
}
|
|
18028
|
+
if (!inTable && /^\s*\|/.test(line) && i + 1 < lines.length && /^\s*\|[\s:|-]+\|\s*$/.test(lines[i + 1])) {
|
|
18029
|
+
const headers = parseTableRow(line);
|
|
18030
|
+
out.push('<div class="table-wrap"><table><thead><tr>');
|
|
18031
|
+
out.push(headers.map((h) => `<th>${inline(h)}</th>`).join(""));
|
|
18032
|
+
out.push("</tr></thead><tbody>");
|
|
18033
|
+
inTable = true;
|
|
18034
|
+
i += 2;
|
|
18035
|
+
continue;
|
|
18036
|
+
}
|
|
18037
|
+
if (inTable && /^\s*\|/.test(line)) {
|
|
18038
|
+
const cells = parseTableRow(line);
|
|
18039
|
+
out.push("<tr>" + cells.map((c) => `<td>${inline(c)}</td>`).join("") + "</tr>");
|
|
18040
|
+
i++;
|
|
18041
|
+
continue;
|
|
18042
|
+
}
|
|
17623
18043
|
const headingMatch = line.match(/^(#{1,3})\s+(.+)$/);
|
|
17624
18044
|
if (headingMatch) {
|
|
17625
18045
|
const level = headingMatch[1].length;
|
|
17626
18046
|
out.push(`<h${level}>${inline(headingMatch[2])}</h${level}>`);
|
|
18047
|
+
i++;
|
|
17627
18048
|
continue;
|
|
17628
18049
|
}
|
|
17629
18050
|
const ulMatch = line.match(/^\s*[-*]\s+(.+)$/);
|
|
@@ -17635,6 +18056,7 @@ function renderMarkdown(md) {
|
|
|
17635
18056
|
listTag = "ul";
|
|
17636
18057
|
}
|
|
17637
18058
|
out.push(`<li>${inline(ulMatch[1])}</li>`);
|
|
18059
|
+
i++;
|
|
17638
18060
|
continue;
|
|
17639
18061
|
}
|
|
17640
18062
|
const olMatch = line.match(/^\s*\d+\.\s+(.+)$/);
|
|
@@ -17646,6 +18068,7 @@ function renderMarkdown(md) {
|
|
|
17646
18068
|
listTag = "ol";
|
|
17647
18069
|
}
|
|
17648
18070
|
out.push(`<li>${inline(olMatch[1])}</li>`);
|
|
18071
|
+
i++;
|
|
17649
18072
|
continue;
|
|
17650
18073
|
}
|
|
17651
18074
|
if (line.trim() === "") {
|
|
@@ -17653,13 +18076,19 @@ function renderMarkdown(md) {
|
|
|
17653
18076
|
out.push(`</${listTag}>`);
|
|
17654
18077
|
inList = false;
|
|
17655
18078
|
}
|
|
18079
|
+
i++;
|
|
17656
18080
|
continue;
|
|
17657
18081
|
}
|
|
17658
18082
|
out.push(`<p>${inline(line)}</p>`);
|
|
18083
|
+
i++;
|
|
17659
18084
|
}
|
|
17660
18085
|
if (inList) out.push(`</${listTag}>`);
|
|
18086
|
+
if (inTable) out.push("</tbody></table></div>");
|
|
17661
18087
|
return out.join("\n");
|
|
17662
18088
|
}
|
|
18089
|
+
function parseTableRow(line) {
|
|
18090
|
+
return line.replace(/^\s*\|/, "").replace(/\|\s*$/, "").split("|").map((cell) => cell.trim());
|
|
18091
|
+
}
|
|
17663
18092
|
function inline(text) {
|
|
17664
18093
|
let s = escapeHtml(text);
|
|
17665
18094
|
s = s.replace(/`([^`]+)`/g, "<code>$1</code>");
|
|
@@ -17673,7 +18102,8 @@ function layout(opts, body) {
|
|
|
17673
18102
|
const topItems = [
|
|
17674
18103
|
{ href: "/", label: "Overview" },
|
|
17675
18104
|
{ href: "/board", label: "Board" },
|
|
17676
|
-
{ href: "/gar", label: "GAR Report" }
|
|
18105
|
+
{ href: "/gar", label: "GAR Report" },
|
|
18106
|
+
{ href: "/health", label: "Health" }
|
|
17677
18107
|
];
|
|
17678
18108
|
const isActive = (href) => opts.activePath === href || href !== "/" && opts.activePath.startsWith(href) ? " active" : "";
|
|
17679
18109
|
const groupsHtml = opts.navGroups.map((group) => {
|
|
@@ -17708,9 +18138,15 @@ function layout(opts, body) {
|
|
|
17708
18138
|
</nav>
|
|
17709
18139
|
</aside>
|
|
17710
18140
|
<main class="main">
|
|
18141
|
+
<button class="expand-toggle" onclick="document.querySelector('.main').classList.toggle('expanded')" title="Toggle wide view">
|
|
18142
|
+
<svg class="icon-expand" viewBox="0 0 16 16" width="16" height="16" fill="currentColor"><path d="M1 1h5v1.5H3.56l3.72 3.72-1.06 1.06L2.5 3.56V6H1V1zm14 14h-5v-1.5h2.44l-3.72-3.72 1.06-1.06 3.72 3.72V10H15v5z"/></svg>
|
|
18143
|
+
<svg class="icon-collapse" viewBox="0 0 16 16" width="16" height="16" fill="currentColor"><path d="M6 7H1V5.5h2.44L0.22 2.28l1.06-1.06L4.5 4.44V2H6v5zm4-1h5v1.5h-2.44l3.22 3.22-1.06 1.06L11.5 8.56V11H10V6z"/></svg>
|
|
18144
|
+
</button>
|
|
17711
18145
|
${body}
|
|
17712
18146
|
</main>
|
|
17713
18147
|
</div>
|
|
18148
|
+
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
|
|
18149
|
+
<script>mermaid.initialize({ startOnLoad: true, theme: 'dark' });</script>
|
|
17714
18150
|
</body>
|
|
17715
18151
|
</html>`;
|
|
17716
18152
|
}
|
|
@@ -17825,7 +18261,33 @@ a:hover { text-decoration: underline; }
|
|
|
17825
18261
|
flex: 1;
|
|
17826
18262
|
padding: 2rem 2.5rem;
|
|
17827
18263
|
max-width: 1200px;
|
|
18264
|
+
position: relative;
|
|
18265
|
+
transition: max-width 0.2s ease;
|
|
18266
|
+
}
|
|
18267
|
+
.main.expanded {
|
|
18268
|
+
max-width: none;
|
|
18269
|
+
}
|
|
18270
|
+
.expand-toggle {
|
|
18271
|
+
position: absolute;
|
|
18272
|
+
top: 1rem;
|
|
18273
|
+
right: 1rem;
|
|
18274
|
+
background: var(--bg-card);
|
|
18275
|
+
border: 1px solid var(--border);
|
|
18276
|
+
border-radius: var(--radius);
|
|
18277
|
+
color: var(--text-dim);
|
|
18278
|
+
cursor: pointer;
|
|
18279
|
+
padding: 0.4rem;
|
|
18280
|
+
display: flex;
|
|
18281
|
+
align-items: center;
|
|
18282
|
+
justify-content: center;
|
|
18283
|
+
transition: color 0.15s, border-color 0.15s;
|
|
17828
18284
|
}
|
|
18285
|
+
.expand-toggle:hover {
|
|
18286
|
+
color: var(--text);
|
|
18287
|
+
border-color: var(--text-dim);
|
|
18288
|
+
}
|
|
18289
|
+
.main.expanded .icon-expand { display: none; }
|
|
18290
|
+
.main:not(.expanded) .icon-collapse { display: none; }
|
|
17829
18291
|
|
|
17830
18292
|
/* Page header */
|
|
17831
18293
|
.page-header {
|
|
@@ -17854,12 +18316,26 @@ a:hover { text-decoration: underline; }
|
|
|
17854
18316
|
.breadcrumb a:hover { color: var(--accent); }
|
|
17855
18317
|
.breadcrumb .sep { margin: 0 0.4rem; }
|
|
17856
18318
|
|
|
18319
|
+
/* Card groups */
|
|
18320
|
+
.card-group {
|
|
18321
|
+
margin-bottom: 1.5rem;
|
|
18322
|
+
}
|
|
18323
|
+
|
|
18324
|
+
.card-group-label {
|
|
18325
|
+
font-size: 0.7rem;
|
|
18326
|
+
text-transform: uppercase;
|
|
18327
|
+
letter-spacing: 0.08em;
|
|
18328
|
+
color: var(--text-dim);
|
|
18329
|
+
font-weight: 600;
|
|
18330
|
+
margin-bottom: 0.5rem;
|
|
18331
|
+
}
|
|
18332
|
+
|
|
17857
18333
|
/* Cards grid */
|
|
17858
18334
|
.cards {
|
|
17859
18335
|
display: grid;
|
|
17860
18336
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
|
17861
18337
|
gap: 1rem;
|
|
17862
|
-
margin-bottom:
|
|
18338
|
+
margin-bottom: 0.5rem;
|
|
17863
18339
|
}
|
|
17864
18340
|
|
|
17865
18341
|
.card {
|
|
@@ -18140,6 +18616,14 @@ tr:hover td {
|
|
|
18140
18616
|
font-family: var(--mono);
|
|
18141
18617
|
font-size: 0.85em;
|
|
18142
18618
|
}
|
|
18619
|
+
.detail-content hr {
|
|
18620
|
+
border: none;
|
|
18621
|
+
border-top: 1px solid var(--border);
|
|
18622
|
+
margin: 1.25rem 0;
|
|
18623
|
+
}
|
|
18624
|
+
.detail-content .table-wrap {
|
|
18625
|
+
margin: 0.75rem 0;
|
|
18626
|
+
}
|
|
18143
18627
|
|
|
18144
18628
|
/* Filters */
|
|
18145
18629
|
.filters {
|
|
@@ -18184,21 +18668,206 @@ tr:hover td {
|
|
|
18184
18668
|
.priority-high { color: var(--red); }
|
|
18185
18669
|
.priority-medium { color: var(--amber); }
|
|
18186
18670
|
.priority-low { color: var(--green); }
|
|
18671
|
+
|
|
18672
|
+
/* Health */
|
|
18673
|
+
.health-section-title {
|
|
18674
|
+
font-size: 1.1rem;
|
|
18675
|
+
font-weight: 600;
|
|
18676
|
+
margin: 2rem 0 1rem;
|
|
18677
|
+
color: var(--text);
|
|
18678
|
+
}
|
|
18679
|
+
|
|
18680
|
+
/* Mermaid diagrams */
|
|
18681
|
+
.mermaid-container {
|
|
18682
|
+
background: var(--bg-card);
|
|
18683
|
+
border: 1px solid var(--border);
|
|
18684
|
+
border-radius: var(--radius);
|
|
18685
|
+
padding: 1.5rem;
|
|
18686
|
+
margin: 1rem 0;
|
|
18687
|
+
overflow-x: auto;
|
|
18688
|
+
}
|
|
18689
|
+
|
|
18690
|
+
.mermaid-container .mermaid {
|
|
18691
|
+
display: flex;
|
|
18692
|
+
justify-content: center;
|
|
18693
|
+
}
|
|
18694
|
+
|
|
18695
|
+
.mermaid-empty {
|
|
18696
|
+
text-align: center;
|
|
18697
|
+
color: var(--text-dim);
|
|
18698
|
+
font-size: 0.875rem;
|
|
18699
|
+
}
|
|
18700
|
+
|
|
18701
|
+
.mermaid-row {
|
|
18702
|
+
display: grid;
|
|
18703
|
+
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
|
18704
|
+
gap: 1rem;
|
|
18705
|
+
}
|
|
18706
|
+
|
|
18707
|
+
.mermaid-row .mermaid-container {
|
|
18708
|
+
margin: 0;
|
|
18709
|
+
}
|
|
18187
18710
|
`;
|
|
18188
18711
|
}
|
|
18189
18712
|
|
|
18713
|
+
// src/web/templates/mermaid.ts
|
|
18714
|
+
function sanitize(text, maxLen = 40) {
|
|
18715
|
+
const cleaned = text.replace(/["'`]/g, "").replace(/[\r\n]+/g, " ");
|
|
18716
|
+
return cleaned.length > maxLen ? cleaned.slice(0, maxLen - 1) + "\u2026" : cleaned;
|
|
18717
|
+
}
|
|
18718
|
+
function mermaidBlock(definition) {
|
|
18719
|
+
return `<div class="mermaid-container"><pre class="mermaid">
|
|
18720
|
+
${definition}
|
|
18721
|
+
</pre></div>`;
|
|
18722
|
+
}
|
|
18723
|
+
function placeholder(message) {
|
|
18724
|
+
return `<div class="mermaid-container mermaid-empty"><p>${message}</p></div>`;
|
|
18725
|
+
}
|
|
18726
|
+
function buildTimelineGantt(data) {
|
|
18727
|
+
const sprintsWithDates = data.sprints.filter((s) => s.startDate && s.endDate);
|
|
18728
|
+
if (sprintsWithDates.length === 0) {
|
|
18729
|
+
return placeholder("No timeline data available \u2014 sprints need start and end dates.");
|
|
18730
|
+
}
|
|
18731
|
+
const epicMap = new Map(data.epics.map((e) => [e.id, e]));
|
|
18732
|
+
const lines = ["gantt", " title Project Timeline", " dateFormat YYYY-MM-DD"];
|
|
18733
|
+
for (const sprint of sprintsWithDates) {
|
|
18734
|
+
lines.push(` section ${sanitize(sprint.id + " " + sprint.title, 50)}`);
|
|
18735
|
+
const linked = sprint.linkedEpics.map((eid) => epicMap.get(eid)).filter(Boolean);
|
|
18736
|
+
if (linked.length === 0) {
|
|
18737
|
+
lines.push(` ${sanitize(sprint.title)} :${sprint.startDate}, ${sprint.endDate}`);
|
|
18738
|
+
} else {
|
|
18739
|
+
for (const epic of linked) {
|
|
18740
|
+
const tag = epic.status === "in-progress" ? "active, " : epic.status === "done" ? "done, " : "";
|
|
18741
|
+
lines.push(` ${sanitize(epic.id + " " + epic.title)} :${tag}${sprint.startDate}, ${sprint.endDate}`);
|
|
18742
|
+
}
|
|
18743
|
+
}
|
|
18744
|
+
}
|
|
18745
|
+
return mermaidBlock(lines.join("\n"));
|
|
18746
|
+
}
|
|
18747
|
+
function buildArtifactFlowchart(data) {
|
|
18748
|
+
if (data.features.length === 0 && data.epics.length === 0) {
|
|
18749
|
+
return placeholder("No artifact relationships found \u2014 create features and epics to see the hierarchy.");
|
|
18750
|
+
}
|
|
18751
|
+
const lines = ["graph TD"];
|
|
18752
|
+
lines.push(" classDef done fill:#065f46,stroke:#34d399,color:#d1fae5");
|
|
18753
|
+
lines.push(" classDef inprogress fill:#78350f,stroke:#fbbf24,color:#fef3c7");
|
|
18754
|
+
lines.push(" classDef blocked fill:#7f1d1d,stroke:#f87171,color:#fee2e2");
|
|
18755
|
+
lines.push(" classDef default fill:#1e293b,stroke:#475569,color:#e2e8f0");
|
|
18756
|
+
const nodeIds = /* @__PURE__ */ new Set();
|
|
18757
|
+
for (const epic of data.epics) {
|
|
18758
|
+
if (epic.linkedFeature) {
|
|
18759
|
+
const feature = data.features.find((f) => f.id === epic.linkedFeature);
|
|
18760
|
+
if (feature) {
|
|
18761
|
+
const fNode = feature.id.replace(/-/g, "_");
|
|
18762
|
+
const eNode = epic.id.replace(/-/g, "_");
|
|
18763
|
+
if (!nodeIds.has(fNode)) {
|
|
18764
|
+
lines.push(` ${fNode}["${sanitize(feature.id + " " + feature.title)}"]`);
|
|
18765
|
+
nodeIds.add(fNode);
|
|
18766
|
+
}
|
|
18767
|
+
if (!nodeIds.has(eNode)) {
|
|
18768
|
+
lines.push(` ${eNode}["${sanitize(epic.id + " " + epic.title)}"]`);
|
|
18769
|
+
nodeIds.add(eNode);
|
|
18770
|
+
}
|
|
18771
|
+
lines.push(` ${fNode} --> ${eNode}`);
|
|
18772
|
+
}
|
|
18773
|
+
}
|
|
18774
|
+
}
|
|
18775
|
+
for (const sprint of data.sprints) {
|
|
18776
|
+
const sNode = sprint.id.replace(/-/g, "_");
|
|
18777
|
+
for (const epicId of sprint.linkedEpics) {
|
|
18778
|
+
const epic = data.epics.find((e) => e.id === epicId);
|
|
18779
|
+
if (epic) {
|
|
18780
|
+
const eNode = epic.id.replace(/-/g, "_");
|
|
18781
|
+
if (!nodeIds.has(eNode)) {
|
|
18782
|
+
lines.push(` ${eNode}["${sanitize(epic.id + " " + epic.title)}"]`);
|
|
18783
|
+
nodeIds.add(eNode);
|
|
18784
|
+
}
|
|
18785
|
+
if (!nodeIds.has(sNode)) {
|
|
18786
|
+
lines.push(` ${sNode}["${sanitize(sprint.id + " " + sprint.title)}"]`);
|
|
18787
|
+
nodeIds.add(sNode);
|
|
18788
|
+
}
|
|
18789
|
+
lines.push(` ${eNode} --> ${sNode}`);
|
|
18790
|
+
}
|
|
18791
|
+
}
|
|
18792
|
+
}
|
|
18793
|
+
if (nodeIds.size === 0) {
|
|
18794
|
+
return placeholder("No artifact relationships found \u2014 link epics to features and sprints.");
|
|
18795
|
+
}
|
|
18796
|
+
const allItems = [
|
|
18797
|
+
...data.features.map((f) => ({ id: f.id, status: f.status })),
|
|
18798
|
+
...data.epics.map((e) => ({ id: e.id, status: e.status })),
|
|
18799
|
+
...data.sprints.map((s) => ({ id: s.id, status: s.status }))
|
|
18800
|
+
];
|
|
18801
|
+
for (const item of allItems) {
|
|
18802
|
+
const node = item.id.replace(/-/g, "_");
|
|
18803
|
+
if (!nodeIds.has(node)) continue;
|
|
18804
|
+
const cls = item.status === "done" || item.status === "completed" ? "done" : item.status === "in-progress" || item.status === "active" ? "inprogress" : item.status === "blocked" ? "blocked" : null;
|
|
18805
|
+
if (cls) {
|
|
18806
|
+
lines.push(` class ${node} ${cls}`);
|
|
18807
|
+
}
|
|
18808
|
+
}
|
|
18809
|
+
return mermaidBlock(lines.join("\n"));
|
|
18810
|
+
}
|
|
18811
|
+
function buildStatusPie(title, counts) {
|
|
18812
|
+
const entries = Object.entries(counts).filter(([, v]) => v > 0);
|
|
18813
|
+
if (entries.length === 0) {
|
|
18814
|
+
return placeholder(`No data for ${title}.`);
|
|
18815
|
+
}
|
|
18816
|
+
const lines = [`pie title ${sanitize(title, 60)}`];
|
|
18817
|
+
for (const [label, count] of entries) {
|
|
18818
|
+
lines.push(` "${sanitize(label, 30)}" : ${count}`);
|
|
18819
|
+
}
|
|
18820
|
+
return mermaidBlock(lines.join("\n"));
|
|
18821
|
+
}
|
|
18822
|
+
function buildHealthGauge(categories) {
|
|
18823
|
+
const valid = categories.filter((c) => c.total > 0);
|
|
18824
|
+
if (valid.length === 0) {
|
|
18825
|
+
return placeholder("No completeness data available.");
|
|
18826
|
+
}
|
|
18827
|
+
const pies = valid.map((cat) => {
|
|
18828
|
+
const incomplete = cat.total - cat.complete;
|
|
18829
|
+
const lines = [
|
|
18830
|
+
`pie title ${sanitize(cat.name, 30)}`,
|
|
18831
|
+
` "Complete" : ${cat.complete}`,
|
|
18832
|
+
` "Incomplete" : ${incomplete}`
|
|
18833
|
+
];
|
|
18834
|
+
return mermaidBlock(lines.join("\n"));
|
|
18835
|
+
});
|
|
18836
|
+
return `<div class="mermaid-row">${pies.join("\n")}</div>`;
|
|
18837
|
+
}
|
|
18838
|
+
|
|
18190
18839
|
// src/web/templates/pages/overview.ts
|
|
18191
|
-
function
|
|
18192
|
-
|
|
18193
|
-
(t) => `
|
|
18840
|
+
function renderCard(t) {
|
|
18841
|
+
return `
|
|
18194
18842
|
<div class="card">
|
|
18195
18843
|
<a href="/docs/${t.type}">
|
|
18196
18844
|
<div class="card-label">${escapeHtml(typeLabel(t.type))}s</div>
|
|
18197
18845
|
<div class="card-value">${t.total}</div>
|
|
18198
18846
|
${t.open > 0 ? `<div class="card-sub">${t.open} open</div>` : `<div class="card-sub">none open</div>`}
|
|
18199
18847
|
</a>
|
|
18200
|
-
</div
|
|
18201
|
-
|
|
18848
|
+
</div>`;
|
|
18849
|
+
}
|
|
18850
|
+
function overviewPage(data, diagrams, navGroups) {
|
|
18851
|
+
const typeMap = new Map(data.types.map((t) => [t.type, t]));
|
|
18852
|
+
const placed = /* @__PURE__ */ new Set();
|
|
18853
|
+
const groupSections = navGroups.map((group) => {
|
|
18854
|
+
const groupCards = group.types.filter((type) => typeMap.has(type)).map((type) => {
|
|
18855
|
+
placed.add(type);
|
|
18856
|
+
return renderCard(typeMap.get(type));
|
|
18857
|
+
});
|
|
18858
|
+
if (groupCards.length === 0) return "";
|
|
18859
|
+
return `
|
|
18860
|
+
<div class="card-group">
|
|
18861
|
+
<div class="card-group-label">${escapeHtml(group.label)}</div>
|
|
18862
|
+
<div class="cards">${groupCards.join("\n")}</div>
|
|
18863
|
+
</div>`;
|
|
18864
|
+
}).filter(Boolean).join("\n");
|
|
18865
|
+
const ungrouped = data.types.filter((t) => !placed.has(t.type));
|
|
18866
|
+
const ungroupedSection = ungrouped.length > 0 ? `
|
|
18867
|
+
<div class="card-group">
|
|
18868
|
+
<div class="card-group-label">Other</div>
|
|
18869
|
+
<div class="cards">${ungrouped.map(renderCard).join("\n")}</div>
|
|
18870
|
+
</div>` : "";
|
|
18202
18871
|
const rows = data.recent.map(
|
|
18203
18872
|
(doc) => `
|
|
18204
18873
|
<tr>
|
|
@@ -18214,9 +18883,14 @@ function overviewPage(data) {
|
|
|
18214
18883
|
<h2>Project Overview</h2>
|
|
18215
18884
|
</div>
|
|
18216
18885
|
|
|
18217
|
-
|
|
18218
|
-
|
|
18219
|
-
|
|
18886
|
+
${groupSections}
|
|
18887
|
+
${ungroupedSection}
|
|
18888
|
+
|
|
18889
|
+
<div class="section-title">Project Timeline</div>
|
|
18890
|
+
${buildTimelineGantt(diagrams)}
|
|
18891
|
+
|
|
18892
|
+
<div class="section-title">Artifact Relationships</div>
|
|
18893
|
+
${buildArtifactFlowchart(diagrams)}
|
|
18220
18894
|
|
|
18221
18895
|
<div class="section-title">Recent Activity</div>
|
|
18222
18896
|
${data.recent.length > 0 ? `
|
|
@@ -18383,6 +19057,76 @@ function garPage(report) {
|
|
|
18383
19057
|
<div class="gar-areas">
|
|
18384
19058
|
${areaCards}
|
|
18385
19059
|
</div>
|
|
19060
|
+
|
|
19061
|
+
<div class="section-title">Status Distribution</div>
|
|
19062
|
+
${buildStatusPie("Action Status", {
|
|
19063
|
+
Open: report.metrics.scope.open,
|
|
19064
|
+
Done: report.metrics.scope.done,
|
|
19065
|
+
"In Progress": Math.max(0, report.metrics.scope.total - report.metrics.scope.open - report.metrics.scope.done)
|
|
19066
|
+
})}
|
|
19067
|
+
`;
|
|
19068
|
+
}
|
|
19069
|
+
|
|
19070
|
+
// src/web/templates/pages/health.ts
|
|
19071
|
+
function healthPage(report, metrics) {
|
|
19072
|
+
const dotClass = `dot-${report.overall}`;
|
|
19073
|
+
function renderSection(title, categories) {
|
|
19074
|
+
const cards = categories.map(
|
|
19075
|
+
(cat) => `
|
|
19076
|
+
<div class="gar-area">
|
|
19077
|
+
<div class="area-header">
|
|
19078
|
+
<div class="area-dot dot-${cat.status}"></div>
|
|
19079
|
+
<div class="area-name">${escapeHtml(cat.name)}</div>
|
|
19080
|
+
</div>
|
|
19081
|
+
<div class="area-summary">${escapeHtml(cat.summary)}</div>
|
|
19082
|
+
${cat.items.length > 0 ? `<ul>${cat.items.map((item) => `<li><span class="ref-id">${escapeHtml(item.id)}</span>${escapeHtml(item.detail)}</li>`).join("")}</ul>` : ""}
|
|
19083
|
+
</div>`
|
|
19084
|
+
).join("\n");
|
|
19085
|
+
return `
|
|
19086
|
+
<div class="health-section-title">${escapeHtml(title)}</div>
|
|
19087
|
+
<div class="gar-areas">${cards}</div>
|
|
19088
|
+
`;
|
|
19089
|
+
}
|
|
19090
|
+
return `
|
|
19091
|
+
<div class="page-header">
|
|
19092
|
+
<h2>Governance Health Check</h2>
|
|
19093
|
+
<div class="subtitle">Generated ${escapeHtml(report.generatedAt)}</div>
|
|
19094
|
+
</div>
|
|
19095
|
+
|
|
19096
|
+
<div class="gar-overall">
|
|
19097
|
+
<div class="dot ${dotClass}"></div>
|
|
19098
|
+
<div class="label">Overall: ${escapeHtml(report.overall)}</div>
|
|
19099
|
+
</div>
|
|
19100
|
+
|
|
19101
|
+
${renderSection("Completeness", report.completeness)}
|
|
19102
|
+
|
|
19103
|
+
<div class="health-section-title">Completeness Overview</div>
|
|
19104
|
+
${buildHealthGauge(
|
|
19105
|
+
metrics ? Object.entries(metrics.completeness).map(([name, cat]) => ({
|
|
19106
|
+
name: name.replace(/\b\w/g, (c) => c.toUpperCase()),
|
|
19107
|
+
complete: cat.complete,
|
|
19108
|
+
total: cat.total
|
|
19109
|
+
})) : report.completeness.map((c) => {
|
|
19110
|
+
const match = c.summary.match(/(\d+)\s*\/\s*(\d+)/);
|
|
19111
|
+
return {
|
|
19112
|
+
name: c.name,
|
|
19113
|
+
complete: match ? parseInt(match[1], 10) : 0,
|
|
19114
|
+
total: match ? parseInt(match[2], 10) : 0
|
|
19115
|
+
};
|
|
19116
|
+
})
|
|
19117
|
+
)}
|
|
19118
|
+
|
|
19119
|
+
${renderSection("Process", report.process)}
|
|
19120
|
+
|
|
19121
|
+
<div class="health-section-title">Process Summary</div>
|
|
19122
|
+
${metrics ? buildStatusPie("Process Health", {
|
|
19123
|
+
Stale: metrics.process.stale.length,
|
|
19124
|
+
"Aging Actions": metrics.process.agingActions.length,
|
|
19125
|
+
Healthy: Math.max(
|
|
19126
|
+
0,
|
|
19127
|
+
(metrics.completeness ? Object.values(metrics.completeness).reduce((sum, c) => sum + c.total, 0) : 0) - metrics.process.stale.length - metrics.process.agingActions.length
|
|
19128
|
+
)
|
|
19129
|
+
}) : ""}
|
|
18386
19130
|
`;
|
|
18387
19131
|
}
|
|
18388
19132
|
|
|
@@ -18449,7 +19193,8 @@ function handleRequest(req, res, store, projectName, navGroups) {
|
|
|
18449
19193
|
}
|
|
18450
19194
|
if (pathname === "/") {
|
|
18451
19195
|
const data = getOverviewData(store);
|
|
18452
|
-
const
|
|
19196
|
+
const diagrams = getDiagramData(store);
|
|
19197
|
+
const body = overviewPage(data, diagrams, navGroups);
|
|
18453
19198
|
respond(res, layout({ title: "Overview", activePath: "/", projectName, navGroups }, body));
|
|
18454
19199
|
return;
|
|
18455
19200
|
}
|
|
@@ -18459,6 +19204,13 @@ function handleRequest(req, res, store, projectName, navGroups) {
|
|
|
18459
19204
|
respond(res, layout({ title: "GAR Report", activePath: "/gar", projectName, navGroups }, body));
|
|
18460
19205
|
return;
|
|
18461
19206
|
}
|
|
19207
|
+
if (pathname === "/health") {
|
|
19208
|
+
const healthMetrics = collectHealthMetrics(store);
|
|
19209
|
+
const report = evaluateHealth(projectName, healthMetrics);
|
|
19210
|
+
const body = healthPage(report, healthMetrics);
|
|
19211
|
+
respond(res, layout({ title: "Health Check", activePath: "/health", projectName, navGroups }, body));
|
|
19212
|
+
return;
|
|
19213
|
+
}
|
|
18462
19214
|
const boardMatch = pathname.match(/^\/board(?:\/([^/]+))?$/);
|
|
18463
19215
|
if (boardMatch) {
|
|
18464
19216
|
const type = boardMatch[1];
|
|
@@ -18698,7 +19450,7 @@ function createJiraTools(store) {
|
|
|
18698
19450
|
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
18699
19451
|
};
|
|
18700
19452
|
},
|
|
18701
|
-
{ annotations: {
|
|
19453
|
+
{ annotations: { readOnlyHint: true } }
|
|
18702
19454
|
),
|
|
18703
19455
|
tool19(
|
|
18704
19456
|
"get_jira_issue",
|
|
@@ -18730,7 +19482,7 @@ function createJiraTools(store) {
|
|
|
18730
19482
|
]
|
|
18731
19483
|
};
|
|
18732
19484
|
},
|
|
18733
|
-
{ annotations: {
|
|
19485
|
+
{ annotations: { readOnlyHint: true } }
|
|
18734
19486
|
),
|
|
18735
19487
|
// --- Jira → Local tools ---
|
|
18736
19488
|
tool19(
|
|
@@ -19461,7 +20213,7 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
19461
20213
|
content: [{ type: "text", text: JSON.stringify(urls, null, 2) }]
|
|
19462
20214
|
};
|
|
19463
20215
|
},
|
|
19464
|
-
{ annotations: {
|
|
20216
|
+
{ annotations: { readOnlyHint: true } }
|
|
19465
20217
|
),
|
|
19466
20218
|
tool20(
|
|
19467
20219
|
"get_dashboard_overview",
|
|
@@ -19483,7 +20235,7 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
19483
20235
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
19484
20236
|
};
|
|
19485
20237
|
},
|
|
19486
|
-
{ annotations: {
|
|
20238
|
+
{ annotations: { readOnlyHint: true } }
|
|
19487
20239
|
),
|
|
19488
20240
|
tool20(
|
|
19489
20241
|
"get_dashboard_gar",
|
|
@@ -19495,7 +20247,7 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
19495
20247
|
content: [{ type: "text", text: JSON.stringify(report, null, 2) }]
|
|
19496
20248
|
};
|
|
19497
20249
|
},
|
|
19498
|
-
{ annotations: {
|
|
20250
|
+
{ annotations: { readOnlyHint: true } }
|
|
19499
20251
|
),
|
|
19500
20252
|
tool20(
|
|
19501
20253
|
"get_dashboard_board",
|
|
@@ -19523,7 +20275,7 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
19523
20275
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
19524
20276
|
};
|
|
19525
20277
|
},
|
|
19526
|
-
{ annotations: {
|
|
20278
|
+
{ annotations: { readOnlyHint: true } }
|
|
19527
20279
|
)
|
|
19528
20280
|
];
|
|
19529
20281
|
}
|
|
@@ -21074,7 +21826,7 @@ ${summaries}`
|
|
|
21074
21826
|
content: [{ type: "text", text: guidance }]
|
|
21075
21827
|
};
|
|
21076
21828
|
},
|
|
21077
|
-
{ annotations: {
|
|
21829
|
+
{ annotations: { readOnlyHint: true } }
|
|
21078
21830
|
)
|
|
21079
21831
|
];
|
|
21080
21832
|
}
|
|
@@ -22629,6 +23381,105 @@ function renderConfluence(report) {
|
|
|
22629
23381
|
return lines.join("\n");
|
|
22630
23382
|
}
|
|
22631
23383
|
|
|
23384
|
+
// src/reports/health/render-ascii.ts
|
|
23385
|
+
import chalk17 from "chalk";
|
|
23386
|
+
var STATUS_DOT2 = {
|
|
23387
|
+
green: chalk17.green("\u25CF"),
|
|
23388
|
+
amber: chalk17.yellow("\u25CF"),
|
|
23389
|
+
red: chalk17.red("\u25CF")
|
|
23390
|
+
};
|
|
23391
|
+
var STATUS_LABEL2 = {
|
|
23392
|
+
green: chalk17.green.bold("GREEN"),
|
|
23393
|
+
amber: chalk17.yellow.bold("AMBER"),
|
|
23394
|
+
red: chalk17.red.bold("RED")
|
|
23395
|
+
};
|
|
23396
|
+
var SEPARATOR2 = chalk17.dim("\u2500".repeat(60));
|
|
23397
|
+
function renderAscii2(report) {
|
|
23398
|
+
const lines = [];
|
|
23399
|
+
lines.push("");
|
|
23400
|
+
lines.push(chalk17.bold(` Health Check \xB7 ${report.projectName}`));
|
|
23401
|
+
lines.push(chalk17.dim(` ${report.generatedAt}`));
|
|
23402
|
+
lines.push("");
|
|
23403
|
+
lines.push(` Overall: ${STATUS_LABEL2[report.overall]}`);
|
|
23404
|
+
lines.push("");
|
|
23405
|
+
lines.push(` ${SEPARATOR2}`);
|
|
23406
|
+
lines.push(chalk17.bold(" Completeness"));
|
|
23407
|
+
lines.push(` ${SEPARATOR2}`);
|
|
23408
|
+
for (const cat of report.completeness) {
|
|
23409
|
+
lines.push(` ${STATUS_DOT2[cat.status]} ${chalk17.bold(cat.name.padEnd(16))} ${cat.summary}`);
|
|
23410
|
+
for (const item of cat.items) {
|
|
23411
|
+
lines.push(` ${chalk17.dim("\u2514")} ${item.id} ${chalk17.dim(item.detail)}`);
|
|
23412
|
+
}
|
|
23413
|
+
}
|
|
23414
|
+
lines.push("");
|
|
23415
|
+
lines.push(` ${SEPARATOR2}`);
|
|
23416
|
+
lines.push(chalk17.bold(" Process"));
|
|
23417
|
+
lines.push(` ${SEPARATOR2}`);
|
|
23418
|
+
for (const cat of report.process) {
|
|
23419
|
+
lines.push(` ${STATUS_DOT2[cat.status]} ${chalk17.bold(cat.name.padEnd(22))} ${cat.summary}`);
|
|
23420
|
+
for (const item of cat.items) {
|
|
23421
|
+
lines.push(` ${chalk17.dim("\u2514")} ${item.id} ${chalk17.dim(item.detail)}`);
|
|
23422
|
+
}
|
|
23423
|
+
}
|
|
23424
|
+
lines.push(` ${SEPARATOR2}`);
|
|
23425
|
+
lines.push("");
|
|
23426
|
+
return lines.join("\n");
|
|
23427
|
+
}
|
|
23428
|
+
|
|
23429
|
+
// src/reports/health/render-confluence.ts
|
|
23430
|
+
var EMOJI2 = {
|
|
23431
|
+
green: ":green_circle:",
|
|
23432
|
+
amber: ":yellow_circle:",
|
|
23433
|
+
red: ":red_circle:"
|
|
23434
|
+
};
|
|
23435
|
+
function renderConfluence2(report) {
|
|
23436
|
+
const lines = [];
|
|
23437
|
+
lines.push(`# Health Check \u2014 ${report.projectName}`);
|
|
23438
|
+
lines.push("");
|
|
23439
|
+
lines.push(`**Date:** ${report.generatedAt}`);
|
|
23440
|
+
lines.push(`**Overall:** ${EMOJI2[report.overall]} ${report.overall.toUpperCase()}`);
|
|
23441
|
+
lines.push("");
|
|
23442
|
+
lines.push("## Completeness");
|
|
23443
|
+
lines.push("");
|
|
23444
|
+
lines.push("| Category | Status | Summary |");
|
|
23445
|
+
lines.push("|----------|--------|---------|");
|
|
23446
|
+
for (const cat of report.completeness) {
|
|
23447
|
+
lines.push(
|
|
23448
|
+
`| ${cat.name} | ${EMOJI2[cat.status]} ${cat.status.toUpperCase()} | ${cat.summary} |`
|
|
23449
|
+
);
|
|
23450
|
+
}
|
|
23451
|
+
lines.push("");
|
|
23452
|
+
for (const cat of report.completeness) {
|
|
23453
|
+
if (cat.items.length === 0) continue;
|
|
23454
|
+
lines.push(`### ${cat.name}`);
|
|
23455
|
+
lines.push("");
|
|
23456
|
+
for (const item of cat.items) {
|
|
23457
|
+
lines.push(`- **${item.id}** ${item.detail}`);
|
|
23458
|
+
}
|
|
23459
|
+
lines.push("");
|
|
23460
|
+
}
|
|
23461
|
+
lines.push("## Process");
|
|
23462
|
+
lines.push("");
|
|
23463
|
+
lines.push("| Metric | Status | Summary |");
|
|
23464
|
+
lines.push("|--------|--------|---------|");
|
|
23465
|
+
for (const cat of report.process) {
|
|
23466
|
+
lines.push(
|
|
23467
|
+
`| ${cat.name} | ${EMOJI2[cat.status]} ${cat.status.toUpperCase()} | ${cat.summary} |`
|
|
23468
|
+
);
|
|
23469
|
+
}
|
|
23470
|
+
lines.push("");
|
|
23471
|
+
for (const cat of report.process) {
|
|
23472
|
+
if (cat.items.length === 0) continue;
|
|
23473
|
+
lines.push(`### ${cat.name}`);
|
|
23474
|
+
lines.push("");
|
|
23475
|
+
for (const item of cat.items) {
|
|
23476
|
+
lines.push(`- **${item.id}** ${item.detail}`);
|
|
23477
|
+
}
|
|
23478
|
+
lines.push("");
|
|
23479
|
+
}
|
|
23480
|
+
return lines.join("\n");
|
|
23481
|
+
}
|
|
23482
|
+
|
|
22632
23483
|
// src/cli/commands/report.ts
|
|
22633
23484
|
async function garReportCommand(options) {
|
|
22634
23485
|
const project = loadProject();
|
|
@@ -22644,6 +23495,20 @@ async function garReportCommand(options) {
|
|
|
22644
23495
|
console.log(renderAscii(report));
|
|
22645
23496
|
}
|
|
22646
23497
|
}
|
|
23498
|
+
async function healthReportCommand(options) {
|
|
23499
|
+
const project = loadProject();
|
|
23500
|
+
const plugin = resolvePlugin(project.config.methodology);
|
|
23501
|
+
const registrations = plugin?.documentTypeRegistrations ?? [];
|
|
23502
|
+
const store = new DocumentStore(project.marvinDir, registrations);
|
|
23503
|
+
const metrics = collectHealthMetrics(store);
|
|
23504
|
+
const report = evaluateHealth(project.config.name, metrics);
|
|
23505
|
+
const format = options.format ?? "ascii";
|
|
23506
|
+
if (format === "confluence") {
|
|
23507
|
+
console.log(renderConfluence2(report));
|
|
23508
|
+
} else {
|
|
23509
|
+
console.log(renderAscii2(report));
|
|
23510
|
+
}
|
|
23511
|
+
}
|
|
22647
23512
|
|
|
22648
23513
|
// src/cli/commands/web.ts
|
|
22649
23514
|
async function webCommand(options) {
|
|
@@ -22660,7 +23525,7 @@ function createProgram() {
|
|
|
22660
23525
|
const program2 = new Command();
|
|
22661
23526
|
program2.name("marvin").description(
|
|
22662
23527
|
"AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
|
|
22663
|
-
).version("0.3.
|
|
23528
|
+
).version("0.3.5");
|
|
22664
23529
|
program2.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
|
|
22665
23530
|
await initCommand();
|
|
22666
23531
|
});
|
|
@@ -22737,6 +23602,12 @@ function createProgram() {
|
|
|
22737
23602
|
).action(async (options) => {
|
|
22738
23603
|
await garReportCommand(options);
|
|
22739
23604
|
});
|
|
23605
|
+
reportCmd.command("health").description("Generate a governance health check report").option(
|
|
23606
|
+
"--format <format>",
|
|
23607
|
+
"Output format: ascii or confluence (default: ascii)"
|
|
23608
|
+
).action(async (options) => {
|
|
23609
|
+
await healthReportCommand(options);
|
|
23610
|
+
});
|
|
22740
23611
|
program2.command("web").description("Launch a local web dashboard for project data").option("-p, --port <port>", "Port to listen on (default: 3000)").option("--no-open", "Don't auto-open the browser").action(async (options) => {
|
|
22741
23612
|
await webCommand(options);
|
|
22742
23613
|
});
|