mrvn-cli 0.3.3 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +303 -147
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1278 -289
- package/dist/index.js.map +1 -1
- package/dist/marvin-serve.js +855 -89
- package/dist/marvin-serve.js.map +1 -1
- package/dist/marvin.js +1283 -296
- package/dist/marvin.js.map +1 -1
- package/package.json +1 -1
package/dist/marvin-serve.js
CHANGED
|
@@ -14227,7 +14227,7 @@ function createDecisionTools(store) {
|
|
|
14227
14227
|
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
14228
14228
|
};
|
|
14229
14229
|
},
|
|
14230
|
-
{ annotations: {
|
|
14230
|
+
{ annotations: { readOnlyHint: true } }
|
|
14231
14231
|
),
|
|
14232
14232
|
tool(
|
|
14233
14233
|
"get_decision",
|
|
@@ -14254,7 +14254,7 @@ function createDecisionTools(store) {
|
|
|
14254
14254
|
]
|
|
14255
14255
|
};
|
|
14256
14256
|
},
|
|
14257
|
-
{ annotations: {
|
|
14257
|
+
{ annotations: { readOnlyHint: true } }
|
|
14258
14258
|
),
|
|
14259
14259
|
tool(
|
|
14260
14260
|
"create_decision",
|
|
@@ -14295,7 +14295,8 @@ function createDecisionTools(store) {
|
|
|
14295
14295
|
title: external_exports.string().optional().describe("New title"),
|
|
14296
14296
|
status: external_exports.string().optional().describe("New status"),
|
|
14297
14297
|
content: external_exports.string().optional().describe("New content"),
|
|
14298
|
-
owner: external_exports.string().optional().describe("New owner")
|
|
14298
|
+
owner: external_exports.string().optional().describe("New owner"),
|
|
14299
|
+
tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove 'risk', add 'risk-mitigated')")
|
|
14299
14300
|
},
|
|
14300
14301
|
async (args) => {
|
|
14301
14302
|
const { id, content, ...updates } = args;
|
|
@@ -14315,6 +14316,19 @@ function createDecisionTools(store) {
|
|
|
14315
14316
|
|
|
14316
14317
|
// src/agent/tools/actions.ts
|
|
14317
14318
|
import { tool as tool2 } from "@anthropic-ai/claude-agent-sdk";
|
|
14319
|
+
function findMatchingSprints(store, dueDate) {
|
|
14320
|
+
const sprints = store.list({ type: "sprint" });
|
|
14321
|
+
return sprints.filter((s) => {
|
|
14322
|
+
const start = s.frontmatter.startDate;
|
|
14323
|
+
const end = s.frontmatter.endDate;
|
|
14324
|
+
return start && end && dueDate >= start && dueDate <= end;
|
|
14325
|
+
}).map((s) => ({
|
|
14326
|
+
id: s.frontmatter.id,
|
|
14327
|
+
title: s.frontmatter.title,
|
|
14328
|
+
startDate: s.frontmatter.startDate,
|
|
14329
|
+
endDate: s.frontmatter.endDate
|
|
14330
|
+
}));
|
|
14331
|
+
}
|
|
14318
14332
|
function createActionTools(store) {
|
|
14319
14333
|
return [
|
|
14320
14334
|
tool2(
|
|
@@ -14330,19 +14344,24 @@ function createActionTools(store) {
|
|
|
14330
14344
|
status: args.status,
|
|
14331
14345
|
owner: args.owner
|
|
14332
14346
|
});
|
|
14333
|
-
const summary = docs.map((d) =>
|
|
14334
|
-
|
|
14335
|
-
|
|
14336
|
-
|
|
14337
|
-
|
|
14338
|
-
|
|
14339
|
-
|
|
14340
|
-
|
|
14347
|
+
const summary = docs.map((d) => {
|
|
14348
|
+
const sprintIds = (d.frontmatter.tags ?? []).filter((t) => t.startsWith("sprint:")).map((t) => t.slice(7));
|
|
14349
|
+
return {
|
|
14350
|
+
id: d.frontmatter.id,
|
|
14351
|
+
title: d.frontmatter.title,
|
|
14352
|
+
status: d.frontmatter.status,
|
|
14353
|
+
owner: d.frontmatter.owner,
|
|
14354
|
+
priority: d.frontmatter.priority,
|
|
14355
|
+
dueDate: d.frontmatter.dueDate,
|
|
14356
|
+
sprints: sprintIds.length > 0 ? sprintIds : void 0,
|
|
14357
|
+
created: d.frontmatter.created
|
|
14358
|
+
};
|
|
14359
|
+
});
|
|
14341
14360
|
return {
|
|
14342
14361
|
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
14343
14362
|
};
|
|
14344
14363
|
},
|
|
14345
|
-
{ annotations: {
|
|
14364
|
+
{ annotations: { readOnlyHint: true } }
|
|
14346
14365
|
),
|
|
14347
14366
|
tool2(
|
|
14348
14367
|
"get_action",
|
|
@@ -14369,7 +14388,7 @@ function createActionTools(store) {
|
|
|
14369
14388
|
]
|
|
14370
14389
|
};
|
|
14371
14390
|
},
|
|
14372
|
-
{ annotations: {
|
|
14391
|
+
{ annotations: { readOnlyHint: true } }
|
|
14373
14392
|
),
|
|
14374
14393
|
tool2(
|
|
14375
14394
|
"create_action",
|
|
@@ -14380,9 +14399,18 @@ function createActionTools(store) {
|
|
|
14380
14399
|
status: external_exports.string().optional().describe("Status (default: 'open')"),
|
|
14381
14400
|
owner: external_exports.string().optional().describe("Person responsible"),
|
|
14382
14401
|
priority: external_exports.string().optional().describe("Priority (high, medium, low)"),
|
|
14383
|
-
tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization")
|
|
14402
|
+
tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization"),
|
|
14403
|
+
dueDate: external_exports.string().optional().describe("Due date in ISO format (e.g. '2026-03-15')"),
|
|
14404
|
+
sprints: external_exports.array(external_exports.string()).optional().describe("Sprint IDs to assign (e.g. ['SP-001']). Adds sprint:SP-xxx tags.")
|
|
14384
14405
|
},
|
|
14385
14406
|
async (args) => {
|
|
14407
|
+
const tags = [...args.tags ?? []];
|
|
14408
|
+
if (args.sprints) {
|
|
14409
|
+
for (const sprintId of args.sprints) {
|
|
14410
|
+
const tag = `sprint:${sprintId}`;
|
|
14411
|
+
if (!tags.includes(tag)) tags.push(tag);
|
|
14412
|
+
}
|
|
14413
|
+
}
|
|
14386
14414
|
const doc = store.create(
|
|
14387
14415
|
"action",
|
|
14388
14416
|
{
|
|
@@ -14390,17 +14418,21 @@ function createActionTools(store) {
|
|
|
14390
14418
|
status: args.status,
|
|
14391
14419
|
owner: args.owner,
|
|
14392
14420
|
priority: args.priority,
|
|
14393
|
-
tags:
|
|
14421
|
+
tags: tags.length > 0 ? tags : void 0,
|
|
14422
|
+
dueDate: args.dueDate
|
|
14394
14423
|
},
|
|
14395
14424
|
args.content
|
|
14396
14425
|
);
|
|
14426
|
+
const parts = [`Created action ${doc.frontmatter.id}: ${doc.frontmatter.title}`];
|
|
14427
|
+
if (args.dueDate && (!args.sprints || args.sprints.length === 0)) {
|
|
14428
|
+
const matching = findMatchingSprints(store, args.dueDate);
|
|
14429
|
+
if (matching.length > 0) {
|
|
14430
|
+
const suggestions = matching.map((s) => `${s.id} "${s.title}" (${s.startDate} \u2013 ${s.endDate})`).join(", ");
|
|
14431
|
+
parts.push(`Suggested sprints for dueDate ${args.dueDate}: ${suggestions}. Use the sprints parameter or update_action to assign.`);
|
|
14432
|
+
}
|
|
14433
|
+
}
|
|
14397
14434
|
return {
|
|
14398
|
-
content: [
|
|
14399
|
-
{
|
|
14400
|
-
type: "text",
|
|
14401
|
-
text: `Created action ${doc.frontmatter.id}: ${doc.frontmatter.title}`
|
|
14402
|
-
}
|
|
14403
|
-
]
|
|
14435
|
+
content: [{ type: "text", text: parts.join("\n") }]
|
|
14404
14436
|
};
|
|
14405
14437
|
}
|
|
14406
14438
|
),
|
|
@@ -14413,10 +14445,35 @@ function createActionTools(store) {
|
|
|
14413
14445
|
status: external_exports.string().optional().describe("New status"),
|
|
14414
14446
|
content: external_exports.string().optional().describe("New content"),
|
|
14415
14447
|
owner: external_exports.string().optional().describe("New owner"),
|
|
14416
|
-
priority: external_exports.string().optional().describe("New priority")
|
|
14448
|
+
priority: external_exports.string().optional().describe("New priority"),
|
|
14449
|
+
dueDate: external_exports.string().optional().describe("Due date in ISO format (e.g. '2026-03-15')"),
|
|
14450
|
+
tags: external_exports.array(external_exports.string()).optional().describe("Replace all tags. When provided with sprints, sprint tags are merged into this array."),
|
|
14451
|
+
sprints: external_exports.array(external_exports.string()).optional().describe("Sprint IDs to assign (replaces existing sprint tags). E.g. ['SP-001'].")
|
|
14417
14452
|
},
|
|
14418
14453
|
async (args) => {
|
|
14419
|
-
const { id, content, ...updates } = args;
|
|
14454
|
+
const { id, content, sprints, tags, ...updates } = args;
|
|
14455
|
+
if (tags !== void 0) {
|
|
14456
|
+
const merged = [...tags];
|
|
14457
|
+
if (sprints) {
|
|
14458
|
+
for (const s of sprints) {
|
|
14459
|
+
const tag = `sprint:${s}`;
|
|
14460
|
+
if (!merged.includes(tag)) merged.push(tag);
|
|
14461
|
+
}
|
|
14462
|
+
}
|
|
14463
|
+
updates.tags = merged;
|
|
14464
|
+
} else if (sprints !== void 0) {
|
|
14465
|
+
const existing = store.get(id);
|
|
14466
|
+
if (!existing) {
|
|
14467
|
+
return {
|
|
14468
|
+
content: [{ type: "text", text: `Action ${id} not found` }],
|
|
14469
|
+
isError: true
|
|
14470
|
+
};
|
|
14471
|
+
}
|
|
14472
|
+
const existingTags = existing.frontmatter.tags ?? [];
|
|
14473
|
+
const nonSprintTags = existingTags.filter((t) => !t.startsWith("sprint:"));
|
|
14474
|
+
const newSprintTags = sprints.map((s) => `sprint:${s}`);
|
|
14475
|
+
updates.tags = [...nonSprintTags, ...newSprintTags];
|
|
14476
|
+
}
|
|
14420
14477
|
const doc = store.update(id, updates, content);
|
|
14421
14478
|
return {
|
|
14422
14479
|
content: [
|
|
@@ -14427,6 +14484,35 @@ function createActionTools(store) {
|
|
|
14427
14484
|
]
|
|
14428
14485
|
};
|
|
14429
14486
|
}
|
|
14487
|
+
),
|
|
14488
|
+
tool2(
|
|
14489
|
+
"suggest_sprints_for_action",
|
|
14490
|
+
"Suggest sprints whose date range contains the given due date. Helps assign actions to the right sprint.",
|
|
14491
|
+
{
|
|
14492
|
+
dueDate: external_exports.string().describe("Due date in ISO format (e.g. '2026-03-15')")
|
|
14493
|
+
},
|
|
14494
|
+
async (args) => {
|
|
14495
|
+
const matching = findMatchingSprints(store, args.dueDate);
|
|
14496
|
+
if (matching.length === 0) {
|
|
14497
|
+
return {
|
|
14498
|
+
content: [
|
|
14499
|
+
{
|
|
14500
|
+
type: "text",
|
|
14501
|
+
text: `No sprints found containing dueDate ${args.dueDate}.`
|
|
14502
|
+
}
|
|
14503
|
+
]
|
|
14504
|
+
};
|
|
14505
|
+
}
|
|
14506
|
+
return {
|
|
14507
|
+
content: [
|
|
14508
|
+
{
|
|
14509
|
+
type: "text",
|
|
14510
|
+
text: JSON.stringify(matching, null, 2)
|
|
14511
|
+
}
|
|
14512
|
+
]
|
|
14513
|
+
};
|
|
14514
|
+
},
|
|
14515
|
+
{ annotations: { readOnlyHint: true } }
|
|
14430
14516
|
)
|
|
14431
14517
|
];
|
|
14432
14518
|
}
|
|
@@ -14454,7 +14540,7 @@ function createQuestionTools(store) {
|
|
|
14454
14540
|
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
14455
14541
|
};
|
|
14456
14542
|
},
|
|
14457
|
-
{ annotations: {
|
|
14543
|
+
{ annotations: { readOnlyHint: true } }
|
|
14458
14544
|
),
|
|
14459
14545
|
tool3(
|
|
14460
14546
|
"get_question",
|
|
@@ -14481,7 +14567,7 @@ function createQuestionTools(store) {
|
|
|
14481
14567
|
]
|
|
14482
14568
|
};
|
|
14483
14569
|
},
|
|
14484
|
-
{ annotations: {
|
|
14570
|
+
{ annotations: { readOnlyHint: true } }
|
|
14485
14571
|
),
|
|
14486
14572
|
tool3(
|
|
14487
14573
|
"create_question",
|
|
@@ -14522,7 +14608,8 @@ function createQuestionTools(store) {
|
|
|
14522
14608
|
title: external_exports.string().optional().describe("New title"),
|
|
14523
14609
|
status: external_exports.string().optional().describe("New status (e.g. 'answered')"),
|
|
14524
14610
|
content: external_exports.string().optional().describe("Updated content / answer"),
|
|
14525
|
-
owner: external_exports.string().optional().describe("New owner")
|
|
14611
|
+
owner: external_exports.string().optional().describe("New owner"),
|
|
14612
|
+
tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove 'risk', add 'risk-mitigated')")
|
|
14526
14613
|
},
|
|
14527
14614
|
async (args) => {
|
|
14528
14615
|
const { id, content, ...updates } = args;
|
|
@@ -14574,7 +14661,7 @@ function createDocumentTools(store) {
|
|
|
14574
14661
|
]
|
|
14575
14662
|
};
|
|
14576
14663
|
},
|
|
14577
|
-
{ annotations: {
|
|
14664
|
+
{ annotations: { readOnlyHint: true } }
|
|
14578
14665
|
),
|
|
14579
14666
|
tool4(
|
|
14580
14667
|
"read_document",
|
|
@@ -14601,7 +14688,7 @@ function createDocumentTools(store) {
|
|
|
14601
14688
|
]
|
|
14602
14689
|
};
|
|
14603
14690
|
},
|
|
14604
|
-
{ annotations: {
|
|
14691
|
+
{ annotations: { readOnlyHint: true } }
|
|
14605
14692
|
),
|
|
14606
14693
|
tool4(
|
|
14607
14694
|
"project_summary",
|
|
@@ -14629,7 +14716,7 @@ function createDocumentTools(store) {
|
|
|
14629
14716
|
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
14630
14717
|
};
|
|
14631
14718
|
},
|
|
14632
|
-
{ annotations: {
|
|
14719
|
+
{ annotations: { readOnlyHint: true } }
|
|
14633
14720
|
)
|
|
14634
14721
|
];
|
|
14635
14722
|
}
|
|
@@ -14666,7 +14753,7 @@ function createSourceTools(manifest) {
|
|
|
14666
14753
|
]
|
|
14667
14754
|
};
|
|
14668
14755
|
},
|
|
14669
|
-
{ annotations: {
|
|
14756
|
+
{ annotations: { readOnlyHint: true } }
|
|
14670
14757
|
),
|
|
14671
14758
|
tool5(
|
|
14672
14759
|
"get_source_info",
|
|
@@ -14700,7 +14787,7 @@ function createSourceTools(manifest) {
|
|
|
14700
14787
|
]
|
|
14701
14788
|
};
|
|
14702
14789
|
},
|
|
14703
|
-
{ annotations: {
|
|
14790
|
+
{ annotations: { readOnlyHint: true } }
|
|
14704
14791
|
)
|
|
14705
14792
|
];
|
|
14706
14793
|
}
|
|
@@ -14731,7 +14818,7 @@ function createSessionTools(store) {
|
|
|
14731
14818
|
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
14732
14819
|
};
|
|
14733
14820
|
},
|
|
14734
|
-
{ annotations: {
|
|
14821
|
+
{ annotations: { readOnlyHint: true } }
|
|
14735
14822
|
),
|
|
14736
14823
|
tool6(
|
|
14737
14824
|
"get_session",
|
|
@@ -14749,7 +14836,7 @@ function createSessionTools(store) {
|
|
|
14749
14836
|
content: [{ type: "text", text: JSON.stringify(session, null, 2) }]
|
|
14750
14837
|
};
|
|
14751
14838
|
},
|
|
14752
|
-
{ annotations: {
|
|
14839
|
+
{ annotations: { readOnlyHint: true } }
|
|
14753
14840
|
)
|
|
14754
14841
|
];
|
|
14755
14842
|
}
|
|
@@ -14842,7 +14929,7 @@ function createMeetingTools(store) {
|
|
|
14842
14929
|
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
14843
14930
|
};
|
|
14844
14931
|
},
|
|
14845
|
-
{ annotations: {
|
|
14932
|
+
{ annotations: { readOnlyHint: true } }
|
|
14846
14933
|
),
|
|
14847
14934
|
tool7(
|
|
14848
14935
|
"get_meeting",
|
|
@@ -14869,7 +14956,7 @@ function createMeetingTools(store) {
|
|
|
14869
14956
|
]
|
|
14870
14957
|
};
|
|
14871
14958
|
},
|
|
14872
|
-
{ annotations: {
|
|
14959
|
+
{ annotations: { readOnlyHint: true } }
|
|
14873
14960
|
),
|
|
14874
14961
|
tool7(
|
|
14875
14962
|
"create_meeting",
|
|
@@ -14994,7 +15081,7 @@ function createMeetingTools(store) {
|
|
|
14994
15081
|
content: [{ type: "text", text: sections.join("\n") }]
|
|
14995
15082
|
};
|
|
14996
15083
|
},
|
|
14997
|
-
{ annotations: {
|
|
15084
|
+
{ annotations: { readOnlyHint: true } }
|
|
14998
15085
|
)
|
|
14999
15086
|
];
|
|
15000
15087
|
}
|
|
@@ -15011,12 +15098,20 @@ function collectGarMetrics(store) {
|
|
|
15011
15098
|
const blockedItems = allDocs.filter(
|
|
15012
15099
|
(d) => d.frontmatter.tags?.includes("blocked")
|
|
15013
15100
|
);
|
|
15014
|
-
const
|
|
15101
|
+
const tagOverdueItems = allDocs.filter(
|
|
15015
15102
|
(d) => d.frontmatter.tags?.includes("overdue")
|
|
15016
15103
|
);
|
|
15104
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
15105
|
+
const dateOverdueActions = openActions.filter((d) => {
|
|
15106
|
+
const dueDate = d.frontmatter.dueDate;
|
|
15107
|
+
return typeof dueDate === "string" && dueDate < today;
|
|
15108
|
+
});
|
|
15109
|
+
const overdueItems = [...tagOverdueItems, ...dateOverdueActions].filter(
|
|
15110
|
+
(d, i, arr) => arr.findIndex((x) => x.frontmatter.id === d.frontmatter.id) === i
|
|
15111
|
+
);
|
|
15017
15112
|
const openQuestions = store.list({ type: "question", status: "open" });
|
|
15018
15113
|
const riskItems = allDocs.filter(
|
|
15019
|
-
(d) => d.frontmatter.tags?.includes("risk")
|
|
15114
|
+
(d) => d.frontmatter.tags?.includes("risk") && d.frontmatter.status !== "done" && d.frontmatter.status !== "closed"
|
|
15020
15115
|
);
|
|
15021
15116
|
const unownedActions = openActions.filter((d) => !d.frontmatter.owner);
|
|
15022
15117
|
const total = allActions.length;
|
|
@@ -15122,6 +15217,253 @@ function evaluateGar(projectName, metrics) {
|
|
|
15122
15217
|
};
|
|
15123
15218
|
}
|
|
15124
15219
|
|
|
15220
|
+
// src/reports/health/collector.ts
|
|
15221
|
+
var FIELD_CHECKS = [
|
|
15222
|
+
{
|
|
15223
|
+
type: "action",
|
|
15224
|
+
openStatuses: ["open", "in-progress"],
|
|
15225
|
+
requiredFields: ["owner", "priority", "dueDate", "content"]
|
|
15226
|
+
},
|
|
15227
|
+
{
|
|
15228
|
+
type: "decision",
|
|
15229
|
+
openStatuses: ["open", "proposed"],
|
|
15230
|
+
requiredFields: ["owner", "content"]
|
|
15231
|
+
},
|
|
15232
|
+
{
|
|
15233
|
+
type: "question",
|
|
15234
|
+
openStatuses: ["open"],
|
|
15235
|
+
requiredFields: ["owner", "content"]
|
|
15236
|
+
},
|
|
15237
|
+
{
|
|
15238
|
+
type: "feature",
|
|
15239
|
+
openStatuses: ["draft", "approved"],
|
|
15240
|
+
requiredFields: ["owner", "priority", "content"]
|
|
15241
|
+
},
|
|
15242
|
+
{
|
|
15243
|
+
type: "epic",
|
|
15244
|
+
openStatuses: ["planned", "in-progress"],
|
|
15245
|
+
requiredFields: ["owner", "targetDate", "estimatedEffort", "content"]
|
|
15246
|
+
},
|
|
15247
|
+
{
|
|
15248
|
+
type: "sprint",
|
|
15249
|
+
openStatuses: ["planned", "active"],
|
|
15250
|
+
requiredFields: ["goal", "startDate", "endDate", "linkedEpics"]
|
|
15251
|
+
}
|
|
15252
|
+
];
|
|
15253
|
+
var STALE_THRESHOLD_DAYS = 14;
|
|
15254
|
+
var AGING_THRESHOLD_DAYS = 30;
|
|
15255
|
+
function daysBetween(a, b) {
|
|
15256
|
+
const msPerDay = 864e5;
|
|
15257
|
+
const dateA = new Date(a);
|
|
15258
|
+
const dateB = new Date(b);
|
|
15259
|
+
return Math.floor(Math.abs(dateB.getTime() - dateA.getTime()) / msPerDay);
|
|
15260
|
+
}
|
|
15261
|
+
function checkMissingFields(doc, requiredFields) {
|
|
15262
|
+
const missing = [];
|
|
15263
|
+
for (const field of requiredFields) {
|
|
15264
|
+
if (field === "content") {
|
|
15265
|
+
if (!doc.content || doc.content.trim().length === 0) {
|
|
15266
|
+
missing.push("content");
|
|
15267
|
+
}
|
|
15268
|
+
} else if (field === "linkedEpics") {
|
|
15269
|
+
const val = doc.frontmatter[field];
|
|
15270
|
+
if (!Array.isArray(val) || val.length === 0) {
|
|
15271
|
+
missing.push(field);
|
|
15272
|
+
}
|
|
15273
|
+
} else {
|
|
15274
|
+
const val = doc.frontmatter[field];
|
|
15275
|
+
if (val === void 0 || val === null || val === "") {
|
|
15276
|
+
missing.push(field);
|
|
15277
|
+
}
|
|
15278
|
+
}
|
|
15279
|
+
}
|
|
15280
|
+
return missing;
|
|
15281
|
+
}
|
|
15282
|
+
function collectCompleteness(store) {
|
|
15283
|
+
const result = {};
|
|
15284
|
+
for (const check2 of FIELD_CHECKS) {
|
|
15285
|
+
const allOfType = store.list({ type: check2.type });
|
|
15286
|
+
const openDocs = allOfType.filter(
|
|
15287
|
+
(d) => check2.openStatuses.includes(d.frontmatter.status)
|
|
15288
|
+
);
|
|
15289
|
+
const gaps = [];
|
|
15290
|
+
let complete = 0;
|
|
15291
|
+
for (const doc of openDocs) {
|
|
15292
|
+
const missingFields = checkMissingFields(doc, check2.requiredFields);
|
|
15293
|
+
if (missingFields.length === 0) {
|
|
15294
|
+
complete++;
|
|
15295
|
+
} else {
|
|
15296
|
+
gaps.push({
|
|
15297
|
+
id: doc.frontmatter.id,
|
|
15298
|
+
title: doc.frontmatter.title,
|
|
15299
|
+
missingFields
|
|
15300
|
+
});
|
|
15301
|
+
}
|
|
15302
|
+
}
|
|
15303
|
+
result[check2.type] = {
|
|
15304
|
+
total: openDocs.length,
|
|
15305
|
+
complete,
|
|
15306
|
+
gaps
|
|
15307
|
+
};
|
|
15308
|
+
}
|
|
15309
|
+
return result;
|
|
15310
|
+
}
|
|
15311
|
+
function collectProcess(store) {
|
|
15312
|
+
const today = (/* @__PURE__ */ new Date()).toISOString();
|
|
15313
|
+
const allDocs = store.list();
|
|
15314
|
+
const openStatuses = new Set(FIELD_CHECKS.flatMap((c) => c.openStatuses));
|
|
15315
|
+
const openDocs = allDocs.filter((d) => openStatuses.has(d.frontmatter.status));
|
|
15316
|
+
const stale = [];
|
|
15317
|
+
for (const doc of openDocs) {
|
|
15318
|
+
const updated = doc.frontmatter.updated ?? doc.frontmatter.created;
|
|
15319
|
+
const days = daysBetween(updated, today);
|
|
15320
|
+
if (days >= STALE_THRESHOLD_DAYS) {
|
|
15321
|
+
stale.push({ id: doc.frontmatter.id, title: doc.frontmatter.title, days });
|
|
15322
|
+
}
|
|
15323
|
+
}
|
|
15324
|
+
const openActions = store.list({ type: "action" }).filter((d) => d.frontmatter.status === "open" || d.frontmatter.status === "in-progress");
|
|
15325
|
+
const agingActions = [];
|
|
15326
|
+
for (const doc of openActions) {
|
|
15327
|
+
const days = daysBetween(doc.frontmatter.created, today);
|
|
15328
|
+
if (days >= AGING_THRESHOLD_DAYS) {
|
|
15329
|
+
agingActions.push({ id: doc.frontmatter.id, title: doc.frontmatter.title, days });
|
|
15330
|
+
}
|
|
15331
|
+
}
|
|
15332
|
+
const resolvedDecisions = store.list({ type: "decision" }).filter((d) => !["open", "proposed"].includes(d.frontmatter.status));
|
|
15333
|
+
let decisionTotal = 0;
|
|
15334
|
+
for (const doc of resolvedDecisions) {
|
|
15335
|
+
decisionTotal += daysBetween(doc.frontmatter.created, doc.frontmatter.updated);
|
|
15336
|
+
}
|
|
15337
|
+
const decisionVelocity = {
|
|
15338
|
+
avgDays: resolvedDecisions.length > 0 ? Math.round(decisionTotal / resolvedDecisions.length) : 0,
|
|
15339
|
+
count: resolvedDecisions.length
|
|
15340
|
+
};
|
|
15341
|
+
const answeredQuestions = store.list({ type: "question" }).filter((d) => d.frontmatter.status !== "open");
|
|
15342
|
+
let questionTotal = 0;
|
|
15343
|
+
for (const doc of answeredQuestions) {
|
|
15344
|
+
questionTotal += daysBetween(doc.frontmatter.created, doc.frontmatter.updated);
|
|
15345
|
+
}
|
|
15346
|
+
const questionResolution = {
|
|
15347
|
+
avgDays: answeredQuestions.length > 0 ? Math.round(questionTotal / answeredQuestions.length) : 0,
|
|
15348
|
+
count: answeredQuestions.length
|
|
15349
|
+
};
|
|
15350
|
+
return { stale, agingActions, decisionVelocity, questionResolution };
|
|
15351
|
+
}
|
|
15352
|
+
function collectHealthMetrics(store) {
|
|
15353
|
+
return {
|
|
15354
|
+
completeness: collectCompleteness(store),
|
|
15355
|
+
process: collectProcess(store)
|
|
15356
|
+
};
|
|
15357
|
+
}
|
|
15358
|
+
|
|
15359
|
+
// src/reports/health/evaluator.ts
|
|
15360
|
+
function worstStatus2(statuses) {
|
|
15361
|
+
if (statuses.includes("red")) return "red";
|
|
15362
|
+
if (statuses.includes("amber")) return "amber";
|
|
15363
|
+
return "green";
|
|
15364
|
+
}
|
|
15365
|
+
function completenessStatus(total, complete) {
|
|
15366
|
+
if (total === 0) return "green";
|
|
15367
|
+
const pct = Math.round(complete / total * 100);
|
|
15368
|
+
if (pct >= 100) return "green";
|
|
15369
|
+
if (pct >= 75) return "amber";
|
|
15370
|
+
return "red";
|
|
15371
|
+
}
|
|
15372
|
+
var TYPE_LABELS = {
|
|
15373
|
+
action: "Actions",
|
|
15374
|
+
decision: "Decisions",
|
|
15375
|
+
question: "Questions",
|
|
15376
|
+
feature: "Features",
|
|
15377
|
+
epic: "Epics",
|
|
15378
|
+
sprint: "Sprints"
|
|
15379
|
+
};
|
|
15380
|
+
function evaluateHealth(projectName, metrics) {
|
|
15381
|
+
const completeness = [];
|
|
15382
|
+
for (const [type, catMetrics] of Object.entries(metrics.completeness)) {
|
|
15383
|
+
const { total, complete, gaps } = catMetrics;
|
|
15384
|
+
const status = completenessStatus(total, complete);
|
|
15385
|
+
const pct = total > 0 ? Math.round(complete / total * 100) : 100;
|
|
15386
|
+
completeness.push({
|
|
15387
|
+
name: TYPE_LABELS[type] ?? type,
|
|
15388
|
+
status,
|
|
15389
|
+
summary: `${pct}% complete (${complete}/${total})`,
|
|
15390
|
+
items: gaps.map((g) => ({
|
|
15391
|
+
id: g.id,
|
|
15392
|
+
detail: `missing: ${g.missingFields.join(", ")}`
|
|
15393
|
+
}))
|
|
15394
|
+
});
|
|
15395
|
+
}
|
|
15396
|
+
const process3 = [];
|
|
15397
|
+
const staleCount = metrics.process.stale.length;
|
|
15398
|
+
const staleStatus = staleCount === 0 ? "green" : staleCount <= 3 ? "amber" : "red";
|
|
15399
|
+
process3.push({
|
|
15400
|
+
name: "Stale Items",
|
|
15401
|
+
status: staleStatus,
|
|
15402
|
+
summary: staleCount === 0 ? "no stale items" : `${staleCount} item(s) not updated in 14+ days`,
|
|
15403
|
+
items: metrics.process.stale.map((s) => ({
|
|
15404
|
+
id: s.id,
|
|
15405
|
+
detail: `${s.days} days since last update`
|
|
15406
|
+
}))
|
|
15407
|
+
});
|
|
15408
|
+
const agingCount = metrics.process.agingActions.length;
|
|
15409
|
+
const agingStatus = agingCount === 0 ? "green" : agingCount <= 3 ? "amber" : "red";
|
|
15410
|
+
process3.push({
|
|
15411
|
+
name: "Aging Actions",
|
|
15412
|
+
status: agingStatus,
|
|
15413
|
+
summary: agingCount === 0 ? "no aging actions" : `${agingCount} action(s) open for 30+ days`,
|
|
15414
|
+
items: metrics.process.agingActions.map((a) => ({
|
|
15415
|
+
id: a.id,
|
|
15416
|
+
detail: `open for ${a.days} days`
|
|
15417
|
+
}))
|
|
15418
|
+
});
|
|
15419
|
+
const dv = metrics.process.decisionVelocity;
|
|
15420
|
+
let dvStatus;
|
|
15421
|
+
if (dv.count === 0) {
|
|
15422
|
+
dvStatus = "green";
|
|
15423
|
+
} else if (dv.avgDays <= 7) {
|
|
15424
|
+
dvStatus = "green";
|
|
15425
|
+
} else if (dv.avgDays <= 21) {
|
|
15426
|
+
dvStatus = "amber";
|
|
15427
|
+
} else {
|
|
15428
|
+
dvStatus = "red";
|
|
15429
|
+
}
|
|
15430
|
+
process3.push({
|
|
15431
|
+
name: "Decision Velocity",
|
|
15432
|
+
status: dvStatus,
|
|
15433
|
+
summary: dv.count === 0 ? "no resolved decisions" : `avg ${dv.avgDays} days to resolve (${dv.count} decision(s))`,
|
|
15434
|
+
items: []
|
|
15435
|
+
});
|
|
15436
|
+
const qr = metrics.process.questionResolution;
|
|
15437
|
+
let qrStatus;
|
|
15438
|
+
if (qr.count === 0) {
|
|
15439
|
+
qrStatus = "green";
|
|
15440
|
+
} else if (qr.avgDays <= 7) {
|
|
15441
|
+
qrStatus = "green";
|
|
15442
|
+
} else if (qr.avgDays <= 14) {
|
|
15443
|
+
qrStatus = "amber";
|
|
15444
|
+
} else {
|
|
15445
|
+
qrStatus = "red";
|
|
15446
|
+
}
|
|
15447
|
+
process3.push({
|
|
15448
|
+
name: "Question Resolution",
|
|
15449
|
+
status: qrStatus,
|
|
15450
|
+
summary: qr.count === 0 ? "no answered questions" : `avg ${qr.avgDays} days to answer (${qr.count} question(s))`,
|
|
15451
|
+
items: []
|
|
15452
|
+
});
|
|
15453
|
+
const allStatuses = [
|
|
15454
|
+
...completeness.map((c) => c.status),
|
|
15455
|
+
...process3.map((p) => p.status)
|
|
15456
|
+
];
|
|
15457
|
+
const overall = worstStatus2(allStatuses);
|
|
15458
|
+
return {
|
|
15459
|
+
projectName,
|
|
15460
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
|
|
15461
|
+
overall,
|
|
15462
|
+
completeness,
|
|
15463
|
+
process: process3
|
|
15464
|
+
};
|
|
15465
|
+
}
|
|
15466
|
+
|
|
15125
15467
|
// src/plugins/builtin/tools/reports.ts
|
|
15126
15468
|
function createReportTools(store) {
|
|
15127
15469
|
return [
|
|
@@ -15141,7 +15483,8 @@ function createReportTools(store) {
|
|
|
15141
15483
|
id: d.frontmatter.id,
|
|
15142
15484
|
title: d.frontmatter.title,
|
|
15143
15485
|
owner: d.frontmatter.owner,
|
|
15144
|
-
priority: d.frontmatter.priority
|
|
15486
|
+
priority: d.frontmatter.priority,
|
|
15487
|
+
dueDate: d.frontmatter.dueDate
|
|
15145
15488
|
})),
|
|
15146
15489
|
completedActions: completedActions.map((d) => ({
|
|
15147
15490
|
id: d.frontmatter.id,
|
|
@@ -15160,7 +15503,7 @@ function createReportTools(store) {
|
|
|
15160
15503
|
content: [{ type: "text", text: JSON.stringify(report, null, 2) }]
|
|
15161
15504
|
};
|
|
15162
15505
|
},
|
|
15163
|
-
{ annotations: {
|
|
15506
|
+
{ annotations: { readOnlyHint: true } }
|
|
15164
15507
|
),
|
|
15165
15508
|
tool8(
|
|
15166
15509
|
"generate_risk_register",
|
|
@@ -15169,7 +15512,7 @@ function createReportTools(store) {
|
|
|
15169
15512
|
async () => {
|
|
15170
15513
|
const allDocs = store.list();
|
|
15171
15514
|
const taggedRisks = allDocs.filter(
|
|
15172
|
-
(d) => d.frontmatter.tags?.includes("risk")
|
|
15515
|
+
(d) => d.frontmatter.tags?.includes("risk") && d.frontmatter.status !== "done" && d.frontmatter.status !== "closed"
|
|
15173
15516
|
);
|
|
15174
15517
|
const highPriorityActions = store.list({ type: "action", status: "open" }).filter((d) => d.frontmatter.priority === "high");
|
|
15175
15518
|
const unresolvedQuestions = store.list({ type: "question", status: "open" });
|
|
@@ -15204,7 +15547,7 @@ function createReportTools(store) {
|
|
|
15204
15547
|
content: [{ type: "text", text: JSON.stringify(register, null, 2) }]
|
|
15205
15548
|
};
|
|
15206
15549
|
},
|
|
15207
|
-
{ annotations: {
|
|
15550
|
+
{ annotations: { readOnlyHint: true } }
|
|
15208
15551
|
),
|
|
15209
15552
|
tool8(
|
|
15210
15553
|
"generate_gar_report",
|
|
@@ -15217,7 +15560,7 @@ function createReportTools(store) {
|
|
|
15217
15560
|
content: [{ type: "text", text: JSON.stringify(report, null, 2) }]
|
|
15218
15561
|
};
|
|
15219
15562
|
},
|
|
15220
|
-
{ annotations: {
|
|
15563
|
+
{ annotations: { readOnlyHint: true } }
|
|
15221
15564
|
),
|
|
15222
15565
|
tool8(
|
|
15223
15566
|
"generate_epic_progress",
|
|
@@ -15302,7 +15645,7 @@ function createReportTools(store) {
|
|
|
15302
15645
|
]
|
|
15303
15646
|
};
|
|
15304
15647
|
},
|
|
15305
|
-
{ annotations: {
|
|
15648
|
+
{ annotations: { readOnlyHint: true } }
|
|
15306
15649
|
),
|
|
15307
15650
|
tool8(
|
|
15308
15651
|
"generate_sprint_progress",
|
|
@@ -15347,7 +15690,8 @@ function createReportTools(store) {
|
|
|
15347
15690
|
id: d.frontmatter.id,
|
|
15348
15691
|
title: d.frontmatter.title,
|
|
15349
15692
|
type: d.frontmatter.type,
|
|
15350
|
-
status: d.frontmatter.status
|
|
15693
|
+
status: d.frontmatter.status,
|
|
15694
|
+
dueDate: d.frontmatter.dueDate
|
|
15351
15695
|
}))
|
|
15352
15696
|
}
|
|
15353
15697
|
};
|
|
@@ -15356,7 +15700,7 @@ function createReportTools(store) {
|
|
|
15356
15700
|
content: [{ type: "text", text: JSON.stringify({ sprints }, null, 2) }]
|
|
15357
15701
|
};
|
|
15358
15702
|
},
|
|
15359
|
-
{ annotations: {
|
|
15703
|
+
{ annotations: { readOnlyHint: true } }
|
|
15360
15704
|
),
|
|
15361
15705
|
tool8(
|
|
15362
15706
|
"generate_feature_progress",
|
|
@@ -15396,7 +15740,20 @@ function createReportTools(store) {
|
|
|
15396
15740
|
content: [{ type: "text", text: JSON.stringify({ features }, null, 2) }]
|
|
15397
15741
|
};
|
|
15398
15742
|
},
|
|
15399
|
-
{ annotations: {
|
|
15743
|
+
{ annotations: { readOnlyHint: true } }
|
|
15744
|
+
),
|
|
15745
|
+
tool8(
|
|
15746
|
+
"generate_health_report",
|
|
15747
|
+
"Generate a governance health check report covering artifact completeness and process health metrics",
|
|
15748
|
+
{},
|
|
15749
|
+
async () => {
|
|
15750
|
+
const metrics = collectHealthMetrics(store);
|
|
15751
|
+
const report = evaluateHealth("project", metrics);
|
|
15752
|
+
return {
|
|
15753
|
+
content: [{ type: "text", text: JSON.stringify(report, null, 2) }]
|
|
15754
|
+
};
|
|
15755
|
+
},
|
|
15756
|
+
{ annotations: { readOnlyHint: true } }
|
|
15400
15757
|
),
|
|
15401
15758
|
tool8(
|
|
15402
15759
|
"save_report",
|
|
@@ -15458,7 +15815,7 @@ function createFeatureTools(store) {
|
|
|
15458
15815
|
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
15459
15816
|
};
|
|
15460
15817
|
},
|
|
15461
|
-
{ annotations: {
|
|
15818
|
+
{ annotations: { readOnlyHint: true } }
|
|
15462
15819
|
),
|
|
15463
15820
|
tool9(
|
|
15464
15821
|
"get_feature",
|
|
@@ -15485,7 +15842,7 @@ function createFeatureTools(store) {
|
|
|
15485
15842
|
]
|
|
15486
15843
|
};
|
|
15487
15844
|
},
|
|
15488
|
-
{ annotations: {
|
|
15845
|
+
{ annotations: { readOnlyHint: true } }
|
|
15489
15846
|
),
|
|
15490
15847
|
tool9(
|
|
15491
15848
|
"create_feature",
|
|
@@ -15526,7 +15883,8 @@ function createFeatureTools(store) {
|
|
|
15526
15883
|
status: external_exports.enum(["draft", "approved", "deferred", "done"]).optional().describe("New status"),
|
|
15527
15884
|
content: external_exports.string().optional().describe("New content"),
|
|
15528
15885
|
owner: external_exports.string().optional().describe("New owner"),
|
|
15529
|
-
priority: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("New priority")
|
|
15886
|
+
priority: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("New priority"),
|
|
15887
|
+
tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove 'risk', add 'risk-mitigated')")
|
|
15530
15888
|
},
|
|
15531
15889
|
async (args) => {
|
|
15532
15890
|
const { id, content, ...updates } = args;
|
|
@@ -15576,7 +15934,7 @@ function createEpicTools(store) {
|
|
|
15576
15934
|
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
15577
15935
|
};
|
|
15578
15936
|
},
|
|
15579
|
-
{ annotations: {
|
|
15937
|
+
{ annotations: { readOnlyHint: true } }
|
|
15580
15938
|
),
|
|
15581
15939
|
tool10(
|
|
15582
15940
|
"get_epic",
|
|
@@ -15603,7 +15961,7 @@ function createEpicTools(store) {
|
|
|
15603
15961
|
]
|
|
15604
15962
|
};
|
|
15605
15963
|
},
|
|
15606
|
-
{ annotations: {
|
|
15964
|
+
{ annotations: { readOnlyHint: true } }
|
|
15607
15965
|
),
|
|
15608
15966
|
tool10(
|
|
15609
15967
|
"create_epic",
|
|
@@ -15683,7 +16041,8 @@ function createEpicTools(store) {
|
|
|
15683
16041
|
content: external_exports.string().optional().describe("New content"),
|
|
15684
16042
|
owner: external_exports.string().optional().describe("New owner"),
|
|
15685
16043
|
targetDate: external_exports.string().optional().describe("New target date"),
|
|
15686
|
-
estimatedEffort: external_exports.string().optional().describe("New estimated effort")
|
|
16044
|
+
estimatedEffort: external_exports.string().optional().describe("New estimated effort"),
|
|
16045
|
+
tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove 'risk', add 'risk-mitigated')")
|
|
15687
16046
|
},
|
|
15688
16047
|
async (args) => {
|
|
15689
16048
|
const { id, content, ...updates } = args;
|
|
@@ -15735,7 +16094,7 @@ function createContributionTools(store) {
|
|
|
15735
16094
|
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
15736
16095
|
};
|
|
15737
16096
|
},
|
|
15738
|
-
{ annotations: {
|
|
16097
|
+
{ annotations: { readOnlyHint: true } }
|
|
15739
16098
|
),
|
|
15740
16099
|
tool11(
|
|
15741
16100
|
"get_contribution",
|
|
@@ -15762,7 +16121,7 @@ function createContributionTools(store) {
|
|
|
15762
16121
|
]
|
|
15763
16122
|
};
|
|
15764
16123
|
},
|
|
15765
|
-
{ annotations: {
|
|
16124
|
+
{ annotations: { readOnlyHint: true } }
|
|
15766
16125
|
),
|
|
15767
16126
|
tool11(
|
|
15768
16127
|
"create_contribution",
|
|
@@ -15847,7 +16206,7 @@ function createSprintTools(store) {
|
|
|
15847
16206
|
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
15848
16207
|
};
|
|
15849
16208
|
},
|
|
15850
|
-
{ annotations: {
|
|
16209
|
+
{ annotations: { readOnlyHint: true } }
|
|
15851
16210
|
),
|
|
15852
16211
|
tool12(
|
|
15853
16212
|
"get_sprint",
|
|
@@ -15874,7 +16233,7 @@ function createSprintTools(store) {
|
|
|
15874
16233
|
]
|
|
15875
16234
|
};
|
|
15876
16235
|
},
|
|
15877
|
-
{ annotations: {
|
|
16236
|
+
{ annotations: { readOnlyHint: true } }
|
|
15878
16237
|
),
|
|
15879
16238
|
tool12(
|
|
15880
16239
|
"create_sprint",
|
|
@@ -16171,7 +16530,7 @@ function createSprintPlanningTools(store) {
|
|
|
16171
16530
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
16172
16531
|
};
|
|
16173
16532
|
},
|
|
16174
|
-
{ annotations: {
|
|
16533
|
+
{ annotations: { readOnlyHint: true } }
|
|
16175
16534
|
)
|
|
16176
16535
|
];
|
|
16177
16536
|
}
|
|
@@ -16225,6 +16584,7 @@ var genericAgilePlugin = {
|
|
|
16225
16584
|
- Do NOT create epics \u2014 that is the Tech Lead's responsibility. You can view epics to track progress.
|
|
16226
16585
|
- Use priority levels (critical, high, medium, low) to communicate business value.
|
|
16227
16586
|
- Tag features for categorization and cross-referencing.
|
|
16587
|
+
- Include a \`dueDate\` on actions when target dates are known, to enable schedule tracking and overdue detection.
|
|
16228
16588
|
|
|
16229
16589
|
**Contribution Tools:**
|
|
16230
16590
|
- **list_contributions** / **get_contribution**: Browse and read contribution records.
|
|
@@ -16255,6 +16615,7 @@ var genericAgilePlugin = {
|
|
|
16255
16615
|
- Tag work items (actions, decisions, questions) with \`epic:E-xxx\` to group them under an epic.
|
|
16256
16616
|
- Collaborate with the Delivery Manager on target dates and effort estimates.
|
|
16257
16617
|
- Each epic should have a clear scope and definition of done.
|
|
16618
|
+
- Set \`dueDate\` on technical actions based on sprint timelines or epic target dates. Use the \`sprints\` parameter to assign actions to relevant sprints.
|
|
16258
16619
|
|
|
16259
16620
|
**Contribution Tools:**
|
|
16260
16621
|
- **list_contributions** / **get_contribution**: Browse and read contribution records.
|
|
@@ -16314,6 +16675,11 @@ var genericAgilePlugin = {
|
|
|
16314
16675
|
- **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 %.
|
|
16315
16676
|
- Use \`save_report\` with reportType "sprint-progress" to persist sprint reports.
|
|
16316
16677
|
|
|
16678
|
+
**Date Enforcement:**
|
|
16679
|
+
- 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.
|
|
16680
|
+
- When create_action suggests matching sprints in its response, review and assign accordingly using update_action.
|
|
16681
|
+
- Use \`suggest_sprints_for_action\` to find the right sprint for existing actions that lack sprint assignment.
|
|
16682
|
+
|
|
16317
16683
|
**Sprint Workflow:**
|
|
16318
16684
|
- Create sprints with clear goals and date boundaries.
|
|
16319
16685
|
- Assign epics to sprints via linkedEpics.
|
|
@@ -16333,7 +16699,7 @@ var genericAgilePlugin = {
|
|
|
16333
16699
|
**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).
|
|
16334
16700
|
**Meetings**: Meeting records with attendees, agendas, and notes.
|
|
16335
16701
|
|
|
16336
|
-
**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.
|
|
16702
|
+
**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.
|
|
16337
16703
|
|
|
16338
16704
|
- **list_meetings** / **get_meeting**: Browse and read meeting records.
|
|
16339
16705
|
- **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.
|
|
@@ -16379,7 +16745,7 @@ function createUseCaseTools(store) {
|
|
|
16379
16745
|
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
16380
16746
|
};
|
|
16381
16747
|
},
|
|
16382
|
-
{ annotations: {
|
|
16748
|
+
{ annotations: { readOnlyHint: true } }
|
|
16383
16749
|
),
|
|
16384
16750
|
tool14(
|
|
16385
16751
|
"get_use_case",
|
|
@@ -16406,7 +16772,7 @@ function createUseCaseTools(store) {
|
|
|
16406
16772
|
]
|
|
16407
16773
|
};
|
|
16408
16774
|
},
|
|
16409
|
-
{ annotations: {
|
|
16775
|
+
{ annotations: { readOnlyHint: true } }
|
|
16410
16776
|
),
|
|
16411
16777
|
tool14(
|
|
16412
16778
|
"create_use_case",
|
|
@@ -16504,7 +16870,7 @@ function createTechAssessmentTools(store) {
|
|
|
16504
16870
|
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
16505
16871
|
};
|
|
16506
16872
|
},
|
|
16507
|
-
{ annotations: {
|
|
16873
|
+
{ annotations: { readOnlyHint: true } }
|
|
16508
16874
|
),
|
|
16509
16875
|
tool15(
|
|
16510
16876
|
"get_tech_assessment",
|
|
@@ -16531,7 +16897,7 @@ function createTechAssessmentTools(store) {
|
|
|
16531
16897
|
]
|
|
16532
16898
|
};
|
|
16533
16899
|
},
|
|
16534
|
-
{ annotations: {
|
|
16900
|
+
{ annotations: { readOnlyHint: true } }
|
|
16535
16901
|
),
|
|
16536
16902
|
tool15(
|
|
16537
16903
|
"create_tech_assessment",
|
|
@@ -16665,7 +17031,7 @@ function createExtensionDesignTools(store) {
|
|
|
16665
17031
|
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
16666
17032
|
};
|
|
16667
17033
|
},
|
|
16668
|
-
{ annotations: {
|
|
17034
|
+
{ annotations: { readOnlyHint: true } }
|
|
16669
17035
|
),
|
|
16670
17036
|
tool16(
|
|
16671
17037
|
"get_extension_design",
|
|
@@ -16692,7 +17058,7 @@ function createExtensionDesignTools(store) {
|
|
|
16692
17058
|
]
|
|
16693
17059
|
};
|
|
16694
17060
|
},
|
|
16695
|
-
{ annotations: {
|
|
17061
|
+
{ annotations: { readOnlyHint: true } }
|
|
16696
17062
|
),
|
|
16697
17063
|
tool16(
|
|
16698
17064
|
"create_extension_design",
|
|
@@ -16844,7 +17210,7 @@ function createAemReportTools(store) {
|
|
|
16844
17210
|
]
|
|
16845
17211
|
};
|
|
16846
17212
|
},
|
|
16847
|
-
{ annotations: {
|
|
17213
|
+
{ annotations: { readOnlyHint: true } }
|
|
16848
17214
|
),
|
|
16849
17215
|
tool17(
|
|
16850
17216
|
"generate_tech_readiness",
|
|
@@ -16896,7 +17262,7 @@ function createAemReportTools(store) {
|
|
|
16896
17262
|
]
|
|
16897
17263
|
};
|
|
16898
17264
|
},
|
|
16899
|
-
{ annotations: {
|
|
17265
|
+
{ annotations: { readOnlyHint: true } }
|
|
16900
17266
|
),
|
|
16901
17267
|
tool17(
|
|
16902
17268
|
"generate_phase_status",
|
|
@@ -16951,7 +17317,7 @@ function createAemReportTools(store) {
|
|
|
16951
17317
|
]
|
|
16952
17318
|
};
|
|
16953
17319
|
},
|
|
16954
|
-
{ annotations: {
|
|
17320
|
+
{ annotations: { readOnlyHint: true } }
|
|
16955
17321
|
)
|
|
16956
17322
|
];
|
|
16957
17323
|
}
|
|
@@ -16983,7 +17349,7 @@ function createAemPhaseTools(store, marvinDir) {
|
|
|
16983
17349
|
]
|
|
16984
17350
|
};
|
|
16985
17351
|
},
|
|
16986
|
-
{ annotations: {
|
|
17352
|
+
{ annotations: { readOnlyHint: true } }
|
|
16987
17353
|
),
|
|
16988
17354
|
tool18(
|
|
16989
17355
|
"advance_phase",
|
|
@@ -17428,7 +17794,7 @@ function createJiraTools(store) {
|
|
|
17428
17794
|
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
17429
17795
|
};
|
|
17430
17796
|
},
|
|
17431
|
-
{ annotations: {
|
|
17797
|
+
{ annotations: { readOnlyHint: true } }
|
|
17432
17798
|
),
|
|
17433
17799
|
tool19(
|
|
17434
17800
|
"get_jira_issue",
|
|
@@ -17460,7 +17826,7 @@ function createJiraTools(store) {
|
|
|
17460
17826
|
]
|
|
17461
17827
|
};
|
|
17462
17828
|
},
|
|
17463
|
-
{ annotations: {
|
|
17829
|
+
{ annotations: { readOnlyHint: true } }
|
|
17464
17830
|
),
|
|
17465
17831
|
// --- Jira → Local tools ---
|
|
17466
17832
|
tool19(
|
|
@@ -18019,6 +18385,46 @@ function getBoardData(store, type) {
|
|
|
18019
18385
|
}));
|
|
18020
18386
|
return { columns, type, types };
|
|
18021
18387
|
}
|
|
18388
|
+
function getDiagramData(store) {
|
|
18389
|
+
const allDocs = store.list();
|
|
18390
|
+
const sprints = [];
|
|
18391
|
+
const epics = [];
|
|
18392
|
+
const features = [];
|
|
18393
|
+
const statusCounts = {};
|
|
18394
|
+
for (const doc of allDocs) {
|
|
18395
|
+
const fm = doc.frontmatter;
|
|
18396
|
+
const status = fm.status.toLowerCase();
|
|
18397
|
+
statusCounts[status] = (statusCounts[status] ?? 0) + 1;
|
|
18398
|
+
switch (fm.type) {
|
|
18399
|
+
case "sprint":
|
|
18400
|
+
sprints.push({
|
|
18401
|
+
id: fm.id,
|
|
18402
|
+
title: fm.title,
|
|
18403
|
+
status: fm.status,
|
|
18404
|
+
startDate: fm.startDate,
|
|
18405
|
+
endDate: fm.endDate,
|
|
18406
|
+
linkedEpics: fm.linkedEpics ?? []
|
|
18407
|
+
});
|
|
18408
|
+
break;
|
|
18409
|
+
case "epic":
|
|
18410
|
+
epics.push({
|
|
18411
|
+
id: fm.id,
|
|
18412
|
+
title: fm.title,
|
|
18413
|
+
status: fm.status,
|
|
18414
|
+
linkedFeature: fm.linkedFeature
|
|
18415
|
+
});
|
|
18416
|
+
break;
|
|
18417
|
+
case "feature":
|
|
18418
|
+
features.push({
|
|
18419
|
+
id: fm.id,
|
|
18420
|
+
title: fm.title,
|
|
18421
|
+
status: fm.status
|
|
18422
|
+
});
|
|
18423
|
+
break;
|
|
18424
|
+
}
|
|
18425
|
+
}
|
|
18426
|
+
return { sprints, epics, features, statusCounts };
|
|
18427
|
+
}
|
|
18022
18428
|
|
|
18023
18429
|
// src/web/templates/layout.ts
|
|
18024
18430
|
function escapeHtml(str) {
|
|
@@ -18049,16 +18455,43 @@ function renderMarkdown(md) {
|
|
|
18049
18455
|
const out = [];
|
|
18050
18456
|
let inList = false;
|
|
18051
18457
|
let listTag = "ul";
|
|
18052
|
-
|
|
18053
|
-
|
|
18458
|
+
let inTable = false;
|
|
18459
|
+
let i = 0;
|
|
18460
|
+
while (i < lines.length) {
|
|
18461
|
+
const line = lines[i];
|
|
18054
18462
|
if (inList && !/^\s*[-*]\s/.test(line) && !/^\s*\d+\.\s/.test(line) && line.trim() !== "") {
|
|
18055
18463
|
out.push(`</${listTag}>`);
|
|
18056
18464
|
inList = false;
|
|
18057
18465
|
}
|
|
18466
|
+
if (inTable && !/^\s*\|/.test(line)) {
|
|
18467
|
+
out.push("</tbody></table></div>");
|
|
18468
|
+
inTable = false;
|
|
18469
|
+
}
|
|
18470
|
+
if (/^(-{3,}|\*{3,}|_{3,})\s*$/.test(line.trim())) {
|
|
18471
|
+
i++;
|
|
18472
|
+
out.push("<hr>");
|
|
18473
|
+
continue;
|
|
18474
|
+
}
|
|
18475
|
+
if (!inTable && /^\s*\|/.test(line) && i + 1 < lines.length && /^\s*\|[\s:|-]+\|\s*$/.test(lines[i + 1])) {
|
|
18476
|
+
const headers = parseTableRow(line);
|
|
18477
|
+
out.push('<div class="table-wrap"><table><thead><tr>');
|
|
18478
|
+
out.push(headers.map((h) => `<th>${inline(h)}</th>`).join(""));
|
|
18479
|
+
out.push("</tr></thead><tbody>");
|
|
18480
|
+
inTable = true;
|
|
18481
|
+
i += 2;
|
|
18482
|
+
continue;
|
|
18483
|
+
}
|
|
18484
|
+
if (inTable && /^\s*\|/.test(line)) {
|
|
18485
|
+
const cells = parseTableRow(line);
|
|
18486
|
+
out.push("<tr>" + cells.map((c) => `<td>${inline(c)}</td>`).join("") + "</tr>");
|
|
18487
|
+
i++;
|
|
18488
|
+
continue;
|
|
18489
|
+
}
|
|
18058
18490
|
const headingMatch = line.match(/^(#{1,3})\s+(.+)$/);
|
|
18059
18491
|
if (headingMatch) {
|
|
18060
18492
|
const level = headingMatch[1].length;
|
|
18061
18493
|
out.push(`<h${level}>${inline(headingMatch[2])}</h${level}>`);
|
|
18494
|
+
i++;
|
|
18062
18495
|
continue;
|
|
18063
18496
|
}
|
|
18064
18497
|
const ulMatch = line.match(/^\s*[-*]\s+(.+)$/);
|
|
@@ -18070,6 +18503,7 @@ function renderMarkdown(md) {
|
|
|
18070
18503
|
listTag = "ul";
|
|
18071
18504
|
}
|
|
18072
18505
|
out.push(`<li>${inline(ulMatch[1])}</li>`);
|
|
18506
|
+
i++;
|
|
18073
18507
|
continue;
|
|
18074
18508
|
}
|
|
18075
18509
|
const olMatch = line.match(/^\s*\d+\.\s+(.+)$/);
|
|
@@ -18081,6 +18515,7 @@ function renderMarkdown(md) {
|
|
|
18081
18515
|
listTag = "ol";
|
|
18082
18516
|
}
|
|
18083
18517
|
out.push(`<li>${inline(olMatch[1])}</li>`);
|
|
18518
|
+
i++;
|
|
18084
18519
|
continue;
|
|
18085
18520
|
}
|
|
18086
18521
|
if (line.trim() === "") {
|
|
@@ -18088,13 +18523,19 @@ function renderMarkdown(md) {
|
|
|
18088
18523
|
out.push(`</${listTag}>`);
|
|
18089
18524
|
inList = false;
|
|
18090
18525
|
}
|
|
18526
|
+
i++;
|
|
18091
18527
|
continue;
|
|
18092
18528
|
}
|
|
18093
18529
|
out.push(`<p>${inline(line)}</p>`);
|
|
18530
|
+
i++;
|
|
18094
18531
|
}
|
|
18095
18532
|
if (inList) out.push(`</${listTag}>`);
|
|
18533
|
+
if (inTable) out.push("</tbody></table></div>");
|
|
18096
18534
|
return out.join("\n");
|
|
18097
18535
|
}
|
|
18536
|
+
function parseTableRow(line) {
|
|
18537
|
+
return line.replace(/^\s*\|/, "").replace(/\|\s*$/, "").split("|").map((cell) => cell.trim());
|
|
18538
|
+
}
|
|
18098
18539
|
function inline(text) {
|
|
18099
18540
|
let s = escapeHtml(text);
|
|
18100
18541
|
s = s.replace(/`([^`]+)`/g, "<code>$1</code>");
|
|
@@ -18108,7 +18549,8 @@ function layout(opts, body) {
|
|
|
18108
18549
|
const topItems = [
|
|
18109
18550
|
{ href: "/", label: "Overview" },
|
|
18110
18551
|
{ href: "/board", label: "Board" },
|
|
18111
|
-
{ href: "/gar", label: "GAR Report" }
|
|
18552
|
+
{ href: "/gar", label: "GAR Report" },
|
|
18553
|
+
{ href: "/health", label: "Health" }
|
|
18112
18554
|
];
|
|
18113
18555
|
const isActive = (href) => opts.activePath === href || href !== "/" && opts.activePath.startsWith(href) ? " active" : "";
|
|
18114
18556
|
const groupsHtml = opts.navGroups.map((group) => {
|
|
@@ -18143,9 +18585,15 @@ function layout(opts, body) {
|
|
|
18143
18585
|
</nav>
|
|
18144
18586
|
</aside>
|
|
18145
18587
|
<main class="main">
|
|
18588
|
+
<button class="expand-toggle" onclick="document.querySelector('.main').classList.toggle('expanded')" title="Toggle wide view">
|
|
18589
|
+
<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>
|
|
18590
|
+
<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>
|
|
18591
|
+
</button>
|
|
18146
18592
|
${body}
|
|
18147
18593
|
</main>
|
|
18148
18594
|
</div>
|
|
18595
|
+
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
|
|
18596
|
+
<script>mermaid.initialize({ startOnLoad: true, theme: 'dark' });</script>
|
|
18149
18597
|
</body>
|
|
18150
18598
|
</html>`;
|
|
18151
18599
|
}
|
|
@@ -18260,7 +18708,33 @@ a:hover { text-decoration: underline; }
|
|
|
18260
18708
|
flex: 1;
|
|
18261
18709
|
padding: 2rem 2.5rem;
|
|
18262
18710
|
max-width: 1200px;
|
|
18711
|
+
position: relative;
|
|
18712
|
+
transition: max-width 0.2s ease;
|
|
18263
18713
|
}
|
|
18714
|
+
.main.expanded {
|
|
18715
|
+
max-width: none;
|
|
18716
|
+
}
|
|
18717
|
+
.expand-toggle {
|
|
18718
|
+
position: absolute;
|
|
18719
|
+
top: 1rem;
|
|
18720
|
+
right: 1rem;
|
|
18721
|
+
background: var(--bg-card);
|
|
18722
|
+
border: 1px solid var(--border);
|
|
18723
|
+
border-radius: var(--radius);
|
|
18724
|
+
color: var(--text-dim);
|
|
18725
|
+
cursor: pointer;
|
|
18726
|
+
padding: 0.4rem;
|
|
18727
|
+
display: flex;
|
|
18728
|
+
align-items: center;
|
|
18729
|
+
justify-content: center;
|
|
18730
|
+
transition: color 0.15s, border-color 0.15s;
|
|
18731
|
+
}
|
|
18732
|
+
.expand-toggle:hover {
|
|
18733
|
+
color: var(--text);
|
|
18734
|
+
border-color: var(--text-dim);
|
|
18735
|
+
}
|
|
18736
|
+
.main.expanded .icon-expand { display: none; }
|
|
18737
|
+
.main:not(.expanded) .icon-collapse { display: none; }
|
|
18264
18738
|
|
|
18265
18739
|
/* Page header */
|
|
18266
18740
|
.page-header {
|
|
@@ -18289,12 +18763,26 @@ a:hover { text-decoration: underline; }
|
|
|
18289
18763
|
.breadcrumb a:hover { color: var(--accent); }
|
|
18290
18764
|
.breadcrumb .sep { margin: 0 0.4rem; }
|
|
18291
18765
|
|
|
18766
|
+
/* Card groups */
|
|
18767
|
+
.card-group {
|
|
18768
|
+
margin-bottom: 1.5rem;
|
|
18769
|
+
}
|
|
18770
|
+
|
|
18771
|
+
.card-group-label {
|
|
18772
|
+
font-size: 0.7rem;
|
|
18773
|
+
text-transform: uppercase;
|
|
18774
|
+
letter-spacing: 0.08em;
|
|
18775
|
+
color: var(--text-dim);
|
|
18776
|
+
font-weight: 600;
|
|
18777
|
+
margin-bottom: 0.5rem;
|
|
18778
|
+
}
|
|
18779
|
+
|
|
18292
18780
|
/* Cards grid */
|
|
18293
18781
|
.cards {
|
|
18294
18782
|
display: grid;
|
|
18295
18783
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
|
18296
18784
|
gap: 1rem;
|
|
18297
|
-
margin-bottom:
|
|
18785
|
+
margin-bottom: 0.5rem;
|
|
18298
18786
|
}
|
|
18299
18787
|
|
|
18300
18788
|
.card {
|
|
@@ -18575,6 +19063,14 @@ tr:hover td {
|
|
|
18575
19063
|
font-family: var(--mono);
|
|
18576
19064
|
font-size: 0.85em;
|
|
18577
19065
|
}
|
|
19066
|
+
.detail-content hr {
|
|
19067
|
+
border: none;
|
|
19068
|
+
border-top: 1px solid var(--border);
|
|
19069
|
+
margin: 1.25rem 0;
|
|
19070
|
+
}
|
|
19071
|
+
.detail-content .table-wrap {
|
|
19072
|
+
margin: 0.75rem 0;
|
|
19073
|
+
}
|
|
18578
19074
|
|
|
18579
19075
|
/* Filters */
|
|
18580
19076
|
.filters {
|
|
@@ -18619,21 +19115,206 @@ tr:hover td {
|
|
|
18619
19115
|
.priority-high { color: var(--red); }
|
|
18620
19116
|
.priority-medium { color: var(--amber); }
|
|
18621
19117
|
.priority-low { color: var(--green); }
|
|
19118
|
+
|
|
19119
|
+
/* Health */
|
|
19120
|
+
.health-section-title {
|
|
19121
|
+
font-size: 1.1rem;
|
|
19122
|
+
font-weight: 600;
|
|
19123
|
+
margin: 2rem 0 1rem;
|
|
19124
|
+
color: var(--text);
|
|
19125
|
+
}
|
|
19126
|
+
|
|
19127
|
+
/* Mermaid diagrams */
|
|
19128
|
+
.mermaid-container {
|
|
19129
|
+
background: var(--bg-card);
|
|
19130
|
+
border: 1px solid var(--border);
|
|
19131
|
+
border-radius: var(--radius);
|
|
19132
|
+
padding: 1.5rem;
|
|
19133
|
+
margin: 1rem 0;
|
|
19134
|
+
overflow-x: auto;
|
|
19135
|
+
}
|
|
19136
|
+
|
|
19137
|
+
.mermaid-container .mermaid {
|
|
19138
|
+
display: flex;
|
|
19139
|
+
justify-content: center;
|
|
19140
|
+
}
|
|
19141
|
+
|
|
19142
|
+
.mermaid-empty {
|
|
19143
|
+
text-align: center;
|
|
19144
|
+
color: var(--text-dim);
|
|
19145
|
+
font-size: 0.875rem;
|
|
19146
|
+
}
|
|
19147
|
+
|
|
19148
|
+
.mermaid-row {
|
|
19149
|
+
display: grid;
|
|
19150
|
+
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
|
19151
|
+
gap: 1rem;
|
|
19152
|
+
}
|
|
19153
|
+
|
|
19154
|
+
.mermaid-row .mermaid-container {
|
|
19155
|
+
margin: 0;
|
|
19156
|
+
}
|
|
18622
19157
|
`;
|
|
18623
19158
|
}
|
|
18624
19159
|
|
|
19160
|
+
// src/web/templates/mermaid.ts
|
|
19161
|
+
function sanitize(text, maxLen = 40) {
|
|
19162
|
+
const cleaned = text.replace(/["'`]/g, "").replace(/[\r\n]+/g, " ");
|
|
19163
|
+
return cleaned.length > maxLen ? cleaned.slice(0, maxLen - 1) + "\u2026" : cleaned;
|
|
19164
|
+
}
|
|
19165
|
+
function mermaidBlock(definition) {
|
|
19166
|
+
return `<div class="mermaid-container"><pre class="mermaid">
|
|
19167
|
+
${definition}
|
|
19168
|
+
</pre></div>`;
|
|
19169
|
+
}
|
|
19170
|
+
function placeholder(message) {
|
|
19171
|
+
return `<div class="mermaid-container mermaid-empty"><p>${message}</p></div>`;
|
|
19172
|
+
}
|
|
19173
|
+
function buildTimelineGantt(data) {
|
|
19174
|
+
const sprintsWithDates = data.sprints.filter((s) => s.startDate && s.endDate);
|
|
19175
|
+
if (sprintsWithDates.length === 0) {
|
|
19176
|
+
return placeholder("No timeline data available \u2014 sprints need start and end dates.");
|
|
19177
|
+
}
|
|
19178
|
+
const epicMap = new Map(data.epics.map((e) => [e.id, e]));
|
|
19179
|
+
const lines = ["gantt", " title Project Timeline", " dateFormat YYYY-MM-DD"];
|
|
19180
|
+
for (const sprint of sprintsWithDates) {
|
|
19181
|
+
lines.push(` section ${sanitize(sprint.id + " " + sprint.title, 50)}`);
|
|
19182
|
+
const linked = sprint.linkedEpics.map((eid) => epicMap.get(eid)).filter(Boolean);
|
|
19183
|
+
if (linked.length === 0) {
|
|
19184
|
+
lines.push(` ${sanitize(sprint.title)} :${sprint.startDate}, ${sprint.endDate}`);
|
|
19185
|
+
} else {
|
|
19186
|
+
for (const epic of linked) {
|
|
19187
|
+
const tag = epic.status === "in-progress" ? "active, " : epic.status === "done" ? "done, " : "";
|
|
19188
|
+
lines.push(` ${sanitize(epic.id + " " + epic.title)} :${tag}${sprint.startDate}, ${sprint.endDate}`);
|
|
19189
|
+
}
|
|
19190
|
+
}
|
|
19191
|
+
}
|
|
19192
|
+
return mermaidBlock(lines.join("\n"));
|
|
19193
|
+
}
|
|
19194
|
+
function buildArtifactFlowchart(data) {
|
|
19195
|
+
if (data.features.length === 0 && data.epics.length === 0) {
|
|
19196
|
+
return placeholder("No artifact relationships found \u2014 create features and epics to see the hierarchy.");
|
|
19197
|
+
}
|
|
19198
|
+
const lines = ["graph TD"];
|
|
19199
|
+
lines.push(" classDef done fill:#065f46,stroke:#34d399,color:#d1fae5");
|
|
19200
|
+
lines.push(" classDef inprogress fill:#78350f,stroke:#fbbf24,color:#fef3c7");
|
|
19201
|
+
lines.push(" classDef blocked fill:#7f1d1d,stroke:#f87171,color:#fee2e2");
|
|
19202
|
+
lines.push(" classDef default fill:#1e293b,stroke:#475569,color:#e2e8f0");
|
|
19203
|
+
const nodeIds = /* @__PURE__ */ new Set();
|
|
19204
|
+
for (const epic of data.epics) {
|
|
19205
|
+
if (epic.linkedFeature) {
|
|
19206
|
+
const feature = data.features.find((f) => f.id === epic.linkedFeature);
|
|
19207
|
+
if (feature) {
|
|
19208
|
+
const fNode = feature.id.replace(/-/g, "_");
|
|
19209
|
+
const eNode = epic.id.replace(/-/g, "_");
|
|
19210
|
+
if (!nodeIds.has(fNode)) {
|
|
19211
|
+
lines.push(` ${fNode}["${sanitize(feature.id + " " + feature.title)}"]`);
|
|
19212
|
+
nodeIds.add(fNode);
|
|
19213
|
+
}
|
|
19214
|
+
if (!nodeIds.has(eNode)) {
|
|
19215
|
+
lines.push(` ${eNode}["${sanitize(epic.id + " " + epic.title)}"]`);
|
|
19216
|
+
nodeIds.add(eNode);
|
|
19217
|
+
}
|
|
19218
|
+
lines.push(` ${fNode} --> ${eNode}`);
|
|
19219
|
+
}
|
|
19220
|
+
}
|
|
19221
|
+
}
|
|
19222
|
+
for (const sprint of data.sprints) {
|
|
19223
|
+
const sNode = sprint.id.replace(/-/g, "_");
|
|
19224
|
+
for (const epicId of sprint.linkedEpics) {
|
|
19225
|
+
const epic = data.epics.find((e) => e.id === epicId);
|
|
19226
|
+
if (epic) {
|
|
19227
|
+
const eNode = epic.id.replace(/-/g, "_");
|
|
19228
|
+
if (!nodeIds.has(eNode)) {
|
|
19229
|
+
lines.push(` ${eNode}["${sanitize(epic.id + " " + epic.title)}"]`);
|
|
19230
|
+
nodeIds.add(eNode);
|
|
19231
|
+
}
|
|
19232
|
+
if (!nodeIds.has(sNode)) {
|
|
19233
|
+
lines.push(` ${sNode}["${sanitize(sprint.id + " " + sprint.title)}"]`);
|
|
19234
|
+
nodeIds.add(sNode);
|
|
19235
|
+
}
|
|
19236
|
+
lines.push(` ${eNode} --> ${sNode}`);
|
|
19237
|
+
}
|
|
19238
|
+
}
|
|
19239
|
+
}
|
|
19240
|
+
if (nodeIds.size === 0) {
|
|
19241
|
+
return placeholder("No artifact relationships found \u2014 link epics to features and sprints.");
|
|
19242
|
+
}
|
|
19243
|
+
const allItems = [
|
|
19244
|
+
...data.features.map((f) => ({ id: f.id, status: f.status })),
|
|
19245
|
+
...data.epics.map((e) => ({ id: e.id, status: e.status })),
|
|
19246
|
+
...data.sprints.map((s) => ({ id: s.id, status: s.status }))
|
|
19247
|
+
];
|
|
19248
|
+
for (const item of allItems) {
|
|
19249
|
+
const node = item.id.replace(/-/g, "_");
|
|
19250
|
+
if (!nodeIds.has(node)) continue;
|
|
19251
|
+
const cls = item.status === "done" || item.status === "completed" ? "done" : item.status === "in-progress" || item.status === "active" ? "inprogress" : item.status === "blocked" ? "blocked" : null;
|
|
19252
|
+
if (cls) {
|
|
19253
|
+
lines.push(` class ${node} ${cls}`);
|
|
19254
|
+
}
|
|
19255
|
+
}
|
|
19256
|
+
return mermaidBlock(lines.join("\n"));
|
|
19257
|
+
}
|
|
19258
|
+
function buildStatusPie(title, counts) {
|
|
19259
|
+
const entries = Object.entries(counts).filter(([, v]) => v > 0);
|
|
19260
|
+
if (entries.length === 0) {
|
|
19261
|
+
return placeholder(`No data for ${title}.`);
|
|
19262
|
+
}
|
|
19263
|
+
const lines = [`pie title ${sanitize(title, 60)}`];
|
|
19264
|
+
for (const [label, count] of entries) {
|
|
19265
|
+
lines.push(` "${sanitize(label, 30)}" : ${count}`);
|
|
19266
|
+
}
|
|
19267
|
+
return mermaidBlock(lines.join("\n"));
|
|
19268
|
+
}
|
|
19269
|
+
function buildHealthGauge(categories) {
|
|
19270
|
+
const valid = categories.filter((c) => c.total > 0);
|
|
19271
|
+
if (valid.length === 0) {
|
|
19272
|
+
return placeholder("No completeness data available.");
|
|
19273
|
+
}
|
|
19274
|
+
const pies = valid.map((cat) => {
|
|
19275
|
+
const incomplete = cat.total - cat.complete;
|
|
19276
|
+
const lines = [
|
|
19277
|
+
`pie title ${sanitize(cat.name, 30)}`,
|
|
19278
|
+
` "Complete" : ${cat.complete}`,
|
|
19279
|
+
` "Incomplete" : ${incomplete}`
|
|
19280
|
+
];
|
|
19281
|
+
return mermaidBlock(lines.join("\n"));
|
|
19282
|
+
});
|
|
19283
|
+
return `<div class="mermaid-row">${pies.join("\n")}</div>`;
|
|
19284
|
+
}
|
|
19285
|
+
|
|
18625
19286
|
// src/web/templates/pages/overview.ts
|
|
18626
|
-
function
|
|
18627
|
-
|
|
18628
|
-
(t) => `
|
|
19287
|
+
function renderCard(t) {
|
|
19288
|
+
return `
|
|
18629
19289
|
<div class="card">
|
|
18630
19290
|
<a href="/docs/${t.type}">
|
|
18631
19291
|
<div class="card-label">${escapeHtml(typeLabel(t.type))}s</div>
|
|
18632
19292
|
<div class="card-value">${t.total}</div>
|
|
18633
19293
|
${t.open > 0 ? `<div class="card-sub">${t.open} open</div>` : `<div class="card-sub">none open</div>`}
|
|
18634
19294
|
</a>
|
|
18635
|
-
</div
|
|
18636
|
-
|
|
19295
|
+
</div>`;
|
|
19296
|
+
}
|
|
19297
|
+
function overviewPage(data, diagrams, navGroups) {
|
|
19298
|
+
const typeMap = new Map(data.types.map((t) => [t.type, t]));
|
|
19299
|
+
const placed = /* @__PURE__ */ new Set();
|
|
19300
|
+
const groupSections = navGroups.map((group) => {
|
|
19301
|
+
const groupCards = group.types.filter((type) => typeMap.has(type)).map((type) => {
|
|
19302
|
+
placed.add(type);
|
|
19303
|
+
return renderCard(typeMap.get(type));
|
|
19304
|
+
});
|
|
19305
|
+
if (groupCards.length === 0) return "";
|
|
19306
|
+
return `
|
|
19307
|
+
<div class="card-group">
|
|
19308
|
+
<div class="card-group-label">${escapeHtml(group.label)}</div>
|
|
19309
|
+
<div class="cards">${groupCards.join("\n")}</div>
|
|
19310
|
+
</div>`;
|
|
19311
|
+
}).filter(Boolean).join("\n");
|
|
19312
|
+
const ungrouped = data.types.filter((t) => !placed.has(t.type));
|
|
19313
|
+
const ungroupedSection = ungrouped.length > 0 ? `
|
|
19314
|
+
<div class="card-group">
|
|
19315
|
+
<div class="card-group-label">Other</div>
|
|
19316
|
+
<div class="cards">${ungrouped.map(renderCard).join("\n")}</div>
|
|
19317
|
+
</div>` : "";
|
|
18637
19318
|
const rows = data.recent.map(
|
|
18638
19319
|
(doc) => `
|
|
18639
19320
|
<tr>
|
|
@@ -18649,9 +19330,14 @@ function overviewPage(data) {
|
|
|
18649
19330
|
<h2>Project Overview</h2>
|
|
18650
19331
|
</div>
|
|
18651
19332
|
|
|
18652
|
-
|
|
18653
|
-
|
|
18654
|
-
|
|
19333
|
+
${groupSections}
|
|
19334
|
+
${ungroupedSection}
|
|
19335
|
+
|
|
19336
|
+
<div class="section-title">Project Timeline</div>
|
|
19337
|
+
${buildTimelineGantt(diagrams)}
|
|
19338
|
+
|
|
19339
|
+
<div class="section-title">Artifact Relationships</div>
|
|
19340
|
+
${buildArtifactFlowchart(diagrams)}
|
|
18655
19341
|
|
|
18656
19342
|
<div class="section-title">Recent Activity</div>
|
|
18657
19343
|
${data.recent.length > 0 ? `
|
|
@@ -18818,6 +19504,76 @@ function garPage(report) {
|
|
|
18818
19504
|
<div class="gar-areas">
|
|
18819
19505
|
${areaCards}
|
|
18820
19506
|
</div>
|
|
19507
|
+
|
|
19508
|
+
<div class="section-title">Status Distribution</div>
|
|
19509
|
+
${buildStatusPie("Action Status", {
|
|
19510
|
+
Open: report.metrics.scope.open,
|
|
19511
|
+
Done: report.metrics.scope.done,
|
|
19512
|
+
"In Progress": Math.max(0, report.metrics.scope.total - report.metrics.scope.open - report.metrics.scope.done)
|
|
19513
|
+
})}
|
|
19514
|
+
`;
|
|
19515
|
+
}
|
|
19516
|
+
|
|
19517
|
+
// src/web/templates/pages/health.ts
|
|
19518
|
+
function healthPage(report, metrics) {
|
|
19519
|
+
const dotClass = `dot-${report.overall}`;
|
|
19520
|
+
function renderSection(title, categories) {
|
|
19521
|
+
const cards = categories.map(
|
|
19522
|
+
(cat) => `
|
|
19523
|
+
<div class="gar-area">
|
|
19524
|
+
<div class="area-header">
|
|
19525
|
+
<div class="area-dot dot-${cat.status}"></div>
|
|
19526
|
+
<div class="area-name">${escapeHtml(cat.name)}</div>
|
|
19527
|
+
</div>
|
|
19528
|
+
<div class="area-summary">${escapeHtml(cat.summary)}</div>
|
|
19529
|
+
${cat.items.length > 0 ? `<ul>${cat.items.map((item) => `<li><span class="ref-id">${escapeHtml(item.id)}</span>${escapeHtml(item.detail)}</li>`).join("")}</ul>` : ""}
|
|
19530
|
+
</div>`
|
|
19531
|
+
).join("\n");
|
|
19532
|
+
return `
|
|
19533
|
+
<div class="health-section-title">${escapeHtml(title)}</div>
|
|
19534
|
+
<div class="gar-areas">${cards}</div>
|
|
19535
|
+
`;
|
|
19536
|
+
}
|
|
19537
|
+
return `
|
|
19538
|
+
<div class="page-header">
|
|
19539
|
+
<h2>Governance Health Check</h2>
|
|
19540
|
+
<div class="subtitle">Generated ${escapeHtml(report.generatedAt)}</div>
|
|
19541
|
+
</div>
|
|
19542
|
+
|
|
19543
|
+
<div class="gar-overall">
|
|
19544
|
+
<div class="dot ${dotClass}"></div>
|
|
19545
|
+
<div class="label">Overall: ${escapeHtml(report.overall)}</div>
|
|
19546
|
+
</div>
|
|
19547
|
+
|
|
19548
|
+
${renderSection("Completeness", report.completeness)}
|
|
19549
|
+
|
|
19550
|
+
<div class="health-section-title">Completeness Overview</div>
|
|
19551
|
+
${buildHealthGauge(
|
|
19552
|
+
metrics ? Object.entries(metrics.completeness).map(([name, cat]) => ({
|
|
19553
|
+
name: name.replace(/\b\w/g, (c) => c.toUpperCase()),
|
|
19554
|
+
complete: cat.complete,
|
|
19555
|
+
total: cat.total
|
|
19556
|
+
})) : report.completeness.map((c) => {
|
|
19557
|
+
const match = c.summary.match(/(\d+)\s*\/\s*(\d+)/);
|
|
19558
|
+
return {
|
|
19559
|
+
name: c.name,
|
|
19560
|
+
complete: match ? parseInt(match[1], 10) : 0,
|
|
19561
|
+
total: match ? parseInt(match[2], 10) : 0
|
|
19562
|
+
};
|
|
19563
|
+
})
|
|
19564
|
+
)}
|
|
19565
|
+
|
|
19566
|
+
${renderSection("Process", report.process)}
|
|
19567
|
+
|
|
19568
|
+
<div class="health-section-title">Process Summary</div>
|
|
19569
|
+
${metrics ? buildStatusPie("Process Health", {
|
|
19570
|
+
Stale: metrics.process.stale.length,
|
|
19571
|
+
"Aging Actions": metrics.process.agingActions.length,
|
|
19572
|
+
Healthy: Math.max(
|
|
19573
|
+
0,
|
|
19574
|
+
(metrics.completeness ? Object.values(metrics.completeness).reduce((sum, c) => sum + c.total, 0) : 0) - metrics.process.stale.length - metrics.process.agingActions.length
|
|
19575
|
+
)
|
|
19576
|
+
}) : ""}
|
|
18821
19577
|
`;
|
|
18822
19578
|
}
|
|
18823
19579
|
|
|
@@ -18884,7 +19640,8 @@ function handleRequest(req, res, store, projectName, navGroups) {
|
|
|
18884
19640
|
}
|
|
18885
19641
|
if (pathname === "/") {
|
|
18886
19642
|
const data = getOverviewData(store);
|
|
18887
|
-
const
|
|
19643
|
+
const diagrams = getDiagramData(store);
|
|
19644
|
+
const body = overviewPage(data, diagrams, navGroups);
|
|
18888
19645
|
respond(res, layout({ title: "Overview", activePath: "/", projectName, navGroups }, body));
|
|
18889
19646
|
return;
|
|
18890
19647
|
}
|
|
@@ -18894,6 +19651,13 @@ function handleRequest(req, res, store, projectName, navGroups) {
|
|
|
18894
19651
|
respond(res, layout({ title: "GAR Report", activePath: "/gar", projectName, navGroups }, body));
|
|
18895
19652
|
return;
|
|
18896
19653
|
}
|
|
19654
|
+
if (pathname === "/health") {
|
|
19655
|
+
const healthMetrics = collectHealthMetrics(store);
|
|
19656
|
+
const report = evaluateHealth(projectName, healthMetrics);
|
|
19657
|
+
const body = healthPage(report, healthMetrics);
|
|
19658
|
+
respond(res, layout({ title: "Health Check", activePath: "/health", projectName, navGroups }, body));
|
|
19659
|
+
return;
|
|
19660
|
+
}
|
|
18897
19661
|
const boardMatch = pathname.match(/^\/board(?:\/([^/]+))?$/);
|
|
18898
19662
|
if (boardMatch) {
|
|
18899
19663
|
const type = boardMatch[1];
|
|
@@ -19041,7 +19805,7 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
19041
19805
|
content: [{ type: "text", text: JSON.stringify(urls, null, 2) }]
|
|
19042
19806
|
};
|
|
19043
19807
|
},
|
|
19044
|
-
{ annotations: {
|
|
19808
|
+
{ annotations: { readOnlyHint: true } }
|
|
19045
19809
|
),
|
|
19046
19810
|
tool20(
|
|
19047
19811
|
"get_dashboard_overview",
|
|
@@ -19063,7 +19827,7 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
19063
19827
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
19064
19828
|
};
|
|
19065
19829
|
},
|
|
19066
|
-
{ annotations: {
|
|
19830
|
+
{ annotations: { readOnlyHint: true } }
|
|
19067
19831
|
),
|
|
19068
19832
|
tool20(
|
|
19069
19833
|
"get_dashboard_gar",
|
|
@@ -19075,7 +19839,7 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
19075
19839
|
content: [{ type: "text", text: JSON.stringify(report, null, 2) }]
|
|
19076
19840
|
};
|
|
19077
19841
|
},
|
|
19078
|
-
{ annotations: {
|
|
19842
|
+
{ annotations: { readOnlyHint: true } }
|
|
19079
19843
|
),
|
|
19080
19844
|
tool20(
|
|
19081
19845
|
"get_dashboard_board",
|
|
@@ -19103,7 +19867,7 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
19103
19867
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
19104
19868
|
};
|
|
19105
19869
|
},
|
|
19106
|
-
{ annotations: {
|
|
19870
|
+
{ annotations: { readOnlyHint: true } }
|
|
19107
19871
|
)
|
|
19108
19872
|
];
|
|
19109
19873
|
}
|
|
@@ -19261,6 +20025,8 @@ var deliveryManager = {
|
|
|
19261
20025
|
|
|
19262
20026
|
## How You Work
|
|
19263
20027
|
- Review open actions (A-xxx) and follow up on overdue items
|
|
20028
|
+
- Ensure every action has a dueDate \u2014 use update_action to backfill existing ones
|
|
20029
|
+
- Assign actions to sprints when sprint planning is active, using the sprints parameter
|
|
19264
20030
|
- Ensure decisions (D-xxx) are properly documented with rationale
|
|
19265
20031
|
- Track questions (Q-xxx) and ensure they get answered
|
|
19266
20032
|
- Monitor project health and flag risks early
|
|
@@ -19486,7 +20252,7 @@ ${summaries}`
|
|
|
19486
20252
|
content: [{ type: "text", text: guidance }]
|
|
19487
20253
|
};
|
|
19488
20254
|
},
|
|
19489
|
-
{ annotations: {
|
|
20255
|
+
{ annotations: { readOnlyHint: true } }
|
|
19490
20256
|
)
|
|
19491
20257
|
];
|
|
19492
20258
|
}
|