mrvn-cli 0.4.2 → 0.4.4
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 +67 -23
- package/dist/index.js +1412 -320
- package/dist/index.js.map +1 -1
- package/dist/marvin-serve.js +1305 -213
- package/dist/marvin-serve.js.map +1 -1
- package/dist/marvin.js +1406 -314
- package/dist/marvin.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -453,7 +453,7 @@ var deliveryManager = {
|
|
|
453
453
|
"Epic scheduling and tracking",
|
|
454
454
|
"Sprint planning and tracking"
|
|
455
455
|
],
|
|
456
|
-
documentTypes: ["action", "decision", "meeting", "question", "feature", "epic", "sprint"],
|
|
456
|
+
documentTypes: ["action", "decision", "meeting", "question", "feature", "epic", "task", "sprint"],
|
|
457
457
|
contributionTypes: ["risk-finding", "blocker-report", "dependency-update", "status-assessment"]
|
|
458
458
|
};
|
|
459
459
|
|
|
@@ -491,9 +491,10 @@ var techLead = {
|
|
|
491
491
|
"Implementation guidance",
|
|
492
492
|
"Non-functional requirements",
|
|
493
493
|
"Epic creation and scoping",
|
|
494
|
+
"Task creation and breakdown",
|
|
494
495
|
"Sprint scoping and technical execution"
|
|
495
496
|
],
|
|
496
|
-
documentTypes: ["decision", "action", "question", "epic", "sprint"],
|
|
497
|
+
documentTypes: ["decision", "action", "question", "epic", "task", "sprint"],
|
|
497
498
|
contributionTypes: ["action-result", "spike-findings", "technical-assessment", "architecture-review"]
|
|
498
499
|
};
|
|
499
500
|
|
|
@@ -1361,10 +1362,10 @@ function mergeDefs(...defs) {
|
|
|
1361
1362
|
function cloneDef(schema) {
|
|
1362
1363
|
return mergeDefs(schema._zod.def);
|
|
1363
1364
|
}
|
|
1364
|
-
function getElementAtPath(obj,
|
|
1365
|
-
if (!
|
|
1365
|
+
function getElementAtPath(obj, path21) {
|
|
1366
|
+
if (!path21)
|
|
1366
1367
|
return obj;
|
|
1367
|
-
return
|
|
1368
|
+
return path21.reduce((acc, key) => acc?.[key], obj);
|
|
1368
1369
|
}
|
|
1369
1370
|
function promiseAllObject(promisesObj) {
|
|
1370
1371
|
const keys = Object.keys(promisesObj);
|
|
@@ -1747,11 +1748,11 @@ function aborted(x, startIndex = 0) {
|
|
|
1747
1748
|
}
|
|
1748
1749
|
return false;
|
|
1749
1750
|
}
|
|
1750
|
-
function prefixIssues(
|
|
1751
|
+
function prefixIssues(path21, issues) {
|
|
1751
1752
|
return issues.map((iss) => {
|
|
1752
1753
|
var _a2;
|
|
1753
1754
|
(_a2 = iss).path ?? (_a2.path = []);
|
|
1754
|
-
iss.path.unshift(
|
|
1755
|
+
iss.path.unshift(path21);
|
|
1755
1756
|
return iss;
|
|
1756
1757
|
});
|
|
1757
1758
|
}
|
|
@@ -1934,7 +1935,7 @@ function formatError(error48, mapper = (issue2) => issue2.message) {
|
|
|
1934
1935
|
}
|
|
1935
1936
|
function treeifyError(error48, mapper = (issue2) => issue2.message) {
|
|
1936
1937
|
const result = { errors: [] };
|
|
1937
|
-
const processError = (error49,
|
|
1938
|
+
const processError = (error49, path21 = []) => {
|
|
1938
1939
|
var _a2, _b;
|
|
1939
1940
|
for (const issue2 of error49.issues) {
|
|
1940
1941
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
@@ -1944,7 +1945,7 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
|
|
|
1944
1945
|
} else if (issue2.code === "invalid_element") {
|
|
1945
1946
|
processError({ issues: issue2.issues }, issue2.path);
|
|
1946
1947
|
} else {
|
|
1947
|
-
const fullpath = [...
|
|
1948
|
+
const fullpath = [...path21, ...issue2.path];
|
|
1948
1949
|
if (fullpath.length === 0) {
|
|
1949
1950
|
result.errors.push(mapper(issue2));
|
|
1950
1951
|
continue;
|
|
@@ -1976,8 +1977,8 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
|
|
|
1976
1977
|
}
|
|
1977
1978
|
function toDotPath(_path) {
|
|
1978
1979
|
const segs = [];
|
|
1979
|
-
const
|
|
1980
|
-
for (const seg of
|
|
1980
|
+
const path21 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
1981
|
+
for (const seg of path21) {
|
|
1981
1982
|
if (typeof seg === "number")
|
|
1982
1983
|
segs.push(`[${seg}]`);
|
|
1983
1984
|
else if (typeof seg === "symbol")
|
|
@@ -13954,13 +13955,13 @@ function resolveRef(ref, ctx) {
|
|
|
13954
13955
|
if (!ref.startsWith("#")) {
|
|
13955
13956
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
13956
13957
|
}
|
|
13957
|
-
const
|
|
13958
|
-
if (
|
|
13958
|
+
const path21 = ref.slice(1).split("/").filter(Boolean);
|
|
13959
|
+
if (path21.length === 0) {
|
|
13959
13960
|
return ctx.rootSchema;
|
|
13960
13961
|
}
|
|
13961
13962
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
13962
|
-
if (
|
|
13963
|
-
const key =
|
|
13963
|
+
if (path21[0] === defsKey) {
|
|
13964
|
+
const key = path21[1];
|
|
13964
13965
|
if (!key || !ctx.defs[key]) {
|
|
13965
13966
|
throw new Error(`Reference not found: ${ref}`);
|
|
13966
13967
|
}
|
|
@@ -14998,7 +14999,7 @@ function createSessionTools(store) {
|
|
|
14998
14999
|
|
|
14999
15000
|
// src/agent/tools/web.ts
|
|
15000
15001
|
import * as http2 from "http";
|
|
15001
|
-
import { tool as
|
|
15002
|
+
import { tool as tool22 } from "@anthropic-ai/claude-agent-sdk";
|
|
15002
15003
|
|
|
15003
15004
|
// src/plugins/builtin/tools/epic-utils.ts
|
|
15004
15005
|
function normalizeLinkedFeatures(value) {
|
|
@@ -15627,6 +15628,7 @@ function inline(text) {
|
|
|
15627
15628
|
function layout(opts, body) {
|
|
15628
15629
|
const topItems = [
|
|
15629
15630
|
{ href: "/", label: "Overview" },
|
|
15631
|
+
{ href: "/timeline", label: "Timeline" },
|
|
15630
15632
|
{ href: "/board", label: "Board" },
|
|
15631
15633
|
{ href: "/gar", label: "GAR Report" },
|
|
15632
15634
|
{ href: "/health", label: "Health" }
|
|
@@ -15663,7 +15665,7 @@ function layout(opts, body) {
|
|
|
15663
15665
|
${groupsHtml}
|
|
15664
15666
|
</nav>
|
|
15665
15667
|
</aside>
|
|
15666
|
-
<main class="main">
|
|
15668
|
+
<main class="main${opts.mainClass ? ` ${opts.mainClass}` : ""}">
|
|
15667
15669
|
<button class="expand-toggle" onclick="document.querySelector('.main').classList.toggle('expanded')" title="Toggle wide view">
|
|
15668
15670
|
<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>
|
|
15669
15671
|
<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>
|
|
@@ -15672,7 +15674,36 @@ function layout(opts, body) {
|
|
|
15672
15674
|
</main>
|
|
15673
15675
|
</div>
|
|
15674
15676
|
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
|
|
15675
|
-
<script>mermaid.initialize({
|
|
15677
|
+
<script>mermaid.initialize({
|
|
15678
|
+
startOnLoad: true,
|
|
15679
|
+
theme: 'dark',
|
|
15680
|
+
themeVariables: {
|
|
15681
|
+
background: '#1a1d27',
|
|
15682
|
+
primaryColor: '#2a2e3a',
|
|
15683
|
+
sectionBkgColor: '#1a1d27',
|
|
15684
|
+
sectionBkgColor2: '#222632',
|
|
15685
|
+
altSectionBkgColor: '#222632',
|
|
15686
|
+
gridColor: '#2a2e3a',
|
|
15687
|
+
taskBorderColor: '#475569',
|
|
15688
|
+
doneTaskBkgColor: '#065f46',
|
|
15689
|
+
doneTaskBorderColor: '#34d399',
|
|
15690
|
+
activeTaskBkgColor: '#78350f',
|
|
15691
|
+
activeTaskBorderColor: '#fbbf24',
|
|
15692
|
+
taskTextColor: '#e1e4ea',
|
|
15693
|
+
sectionBkgColor: '#1a1d27',
|
|
15694
|
+
pie1: '#34d399',
|
|
15695
|
+
pie2: '#475569',
|
|
15696
|
+
pie3: '#fbbf24',
|
|
15697
|
+
pie4: '#f87171',
|
|
15698
|
+
pie5: '#6c8cff',
|
|
15699
|
+
pie6: '#a78bfa',
|
|
15700
|
+
pie7: '#f472b6',
|
|
15701
|
+
pieTitleTextColor: '#e1e4ea',
|
|
15702
|
+
pieSectionTextColor: '#e1e4ea',
|
|
15703
|
+
pieLegendTextColor: '#e1e4ea',
|
|
15704
|
+
pieStrokeColor: '#1a1d27'
|
|
15705
|
+
}
|
|
15706
|
+
});</script>
|
|
15676
15707
|
</body>
|
|
15677
15708
|
</html>`;
|
|
15678
15709
|
}
|
|
@@ -15919,6 +15950,10 @@ a:hover { text-decoration: underline; }
|
|
|
15919
15950
|
/* Table */
|
|
15920
15951
|
.table-wrap {
|
|
15921
15952
|
overflow-x: auto;
|
|
15953
|
+
overflow-y: auto;
|
|
15954
|
+
max-height: calc(100vh - 280px);
|
|
15955
|
+
border: 1px solid var(--border);
|
|
15956
|
+
border-radius: var(--radius);
|
|
15922
15957
|
}
|
|
15923
15958
|
|
|
15924
15959
|
table {
|
|
@@ -15934,6 +15969,10 @@ th {
|
|
|
15934
15969
|
letter-spacing: 0.05em;
|
|
15935
15970
|
color: var(--text-dim);
|
|
15936
15971
|
border-bottom: 1px solid var(--border);
|
|
15972
|
+
position: sticky;
|
|
15973
|
+
top: 0;
|
|
15974
|
+
background: var(--bg-card);
|
|
15975
|
+
z-index: 1;
|
|
15937
15976
|
}
|
|
15938
15977
|
|
|
15939
15978
|
td {
|
|
@@ -15981,6 +16020,8 @@ tr:hover td {
|
|
|
15981
16020
|
border: 1px solid var(--border);
|
|
15982
16021
|
border-radius: var(--radius);
|
|
15983
16022
|
padding: 1.25rem;
|
|
16023
|
+
display: flex;
|
|
16024
|
+
flex-direction: column;
|
|
15984
16025
|
}
|
|
15985
16026
|
|
|
15986
16027
|
.gar-area .area-header {
|
|
@@ -16011,6 +16052,9 @@ tr:hover td {
|
|
|
16011
16052
|
.gar-area ul {
|
|
16012
16053
|
list-style: none;
|
|
16013
16054
|
font-size: 0.8rem;
|
|
16055
|
+
max-height: 200px;
|
|
16056
|
+
overflow-y: auto;
|
|
16057
|
+
scrollbar-width: thin;
|
|
16014
16058
|
}
|
|
16015
16059
|
|
|
16016
16060
|
.gar-area li {
|
|
@@ -16033,13 +16077,14 @@ tr:hover td {
|
|
|
16033
16077
|
display: flex;
|
|
16034
16078
|
gap: 1rem;
|
|
16035
16079
|
overflow-x: auto;
|
|
16080
|
+
scrollbar-width: thin;
|
|
16036
16081
|
padding-bottom: 1rem;
|
|
16037
16082
|
}
|
|
16038
16083
|
|
|
16039
16084
|
.board-column {
|
|
16040
16085
|
min-width: 240px;
|
|
16041
16086
|
max-width: 300px;
|
|
16042
|
-
flex:
|
|
16087
|
+
flex: 0 0 auto;
|
|
16043
16088
|
}
|
|
16044
16089
|
|
|
16045
16090
|
.board-column-header {
|
|
@@ -16052,6 +16097,7 @@ tr:hover td {
|
|
|
16052
16097
|
margin-bottom: 0.5rem;
|
|
16053
16098
|
display: flex;
|
|
16054
16099
|
justify-content: space-between;
|
|
16100
|
+
flex-shrink: 0;
|
|
16055
16101
|
}
|
|
16056
16102
|
|
|
16057
16103
|
.board-column-header .count {
|
|
@@ -16233,6 +16279,241 @@ tr:hover td {
|
|
|
16233
16279
|
.mermaid-row .mermaid-container {
|
|
16234
16280
|
margin: 0;
|
|
16235
16281
|
}
|
|
16282
|
+
|
|
16283
|
+
/* Three-column artifact flow */
|
|
16284
|
+
.flow-diagram {
|
|
16285
|
+
background: var(--bg-card);
|
|
16286
|
+
border: 1px solid var(--border);
|
|
16287
|
+
border-radius: var(--radius);
|
|
16288
|
+
padding: 1.25rem;
|
|
16289
|
+
position: relative;
|
|
16290
|
+
overflow-x: auto;
|
|
16291
|
+
}
|
|
16292
|
+
|
|
16293
|
+
.flow-lines {
|
|
16294
|
+
position: absolute;
|
|
16295
|
+
top: 0;
|
|
16296
|
+
left: 0;
|
|
16297
|
+
pointer-events: none;
|
|
16298
|
+
}
|
|
16299
|
+
|
|
16300
|
+
.flow-columns {
|
|
16301
|
+
display: flex;
|
|
16302
|
+
gap: 3rem;
|
|
16303
|
+
position: relative;
|
|
16304
|
+
min-width: 600px;
|
|
16305
|
+
}
|
|
16306
|
+
|
|
16307
|
+
.flow-column {
|
|
16308
|
+
flex: 1;
|
|
16309
|
+
min-width: 0;
|
|
16310
|
+
display: flex;
|
|
16311
|
+
flex-direction: column;
|
|
16312
|
+
gap: 0.5rem;
|
|
16313
|
+
}
|
|
16314
|
+
|
|
16315
|
+
.flow-column-header {
|
|
16316
|
+
font-size: 0.7rem;
|
|
16317
|
+
text-transform: uppercase;
|
|
16318
|
+
letter-spacing: 0.06em;
|
|
16319
|
+
color: var(--text-dim);
|
|
16320
|
+
font-weight: 600;
|
|
16321
|
+
padding-bottom: 0.4rem;
|
|
16322
|
+
border-bottom: 1px solid var(--border);
|
|
16323
|
+
margin-bottom: 0.25rem;
|
|
16324
|
+
}
|
|
16325
|
+
|
|
16326
|
+
.flow-node {
|
|
16327
|
+
padding: 0.5rem 0.65rem;
|
|
16328
|
+
border-radius: 6px;
|
|
16329
|
+
border-left: 3px solid var(--border);
|
|
16330
|
+
background: var(--bg);
|
|
16331
|
+
transition: border-color 0.15s, background 0.15s;
|
|
16332
|
+
}
|
|
16333
|
+
|
|
16334
|
+
.flow-node:hover {
|
|
16335
|
+
background: var(--bg-hover);
|
|
16336
|
+
}
|
|
16337
|
+
|
|
16338
|
+
.flow-node-id {
|
|
16339
|
+
display: inline-block;
|
|
16340
|
+
font-family: var(--mono);
|
|
16341
|
+
font-size: 0.65rem;
|
|
16342
|
+
color: var(--accent);
|
|
16343
|
+
margin-bottom: 0.15rem;
|
|
16344
|
+
text-decoration: none;
|
|
16345
|
+
}
|
|
16346
|
+
|
|
16347
|
+
.flow-node-id:hover {
|
|
16348
|
+
text-decoration: underline;
|
|
16349
|
+
}
|
|
16350
|
+
|
|
16351
|
+
.flow-node-title {
|
|
16352
|
+
display: block;
|
|
16353
|
+
font-size: 0.8rem;
|
|
16354
|
+
}
|
|
16355
|
+
|
|
16356
|
+
.flow-done { border-left-color: var(--green); }
|
|
16357
|
+
.flow-active { border-left-color: var(--amber); }
|
|
16358
|
+
.flow-blocked { border-left-color: var(--red); }
|
|
16359
|
+
.flow-default { border-left-color: var(--accent-dim); }
|
|
16360
|
+
|
|
16361
|
+
.flow-node { cursor: pointer; transition: opacity 0.2s, border-color 0.15s, background 0.15s; }
|
|
16362
|
+
.flow-dim { opacity: 0.2; }
|
|
16363
|
+
.flow-lit { background: var(--bg-hover); }
|
|
16364
|
+
.flow-line-lit { stroke: var(--accent) !important; stroke-width: 2 !important; }
|
|
16365
|
+
.flow-line-dim { opacity: 0.08; }
|
|
16366
|
+
|
|
16367
|
+
/* Gantt truncation note */
|
|
16368
|
+
.mermaid-note {
|
|
16369
|
+
font-size: 0.75rem;
|
|
16370
|
+
color: var(--text-dim);
|
|
16371
|
+
text-align: right;
|
|
16372
|
+
margin-bottom: 0.5rem;
|
|
16373
|
+
}
|
|
16374
|
+
|
|
16375
|
+
/* HTML Gantt chart */
|
|
16376
|
+
.gantt {
|
|
16377
|
+
background: var(--bg-card);
|
|
16378
|
+
border: 1px solid var(--border);
|
|
16379
|
+
border-radius: var(--radius);
|
|
16380
|
+
padding: 1.25rem 1.25rem 1.25rem 0;
|
|
16381
|
+
position: relative;
|
|
16382
|
+
overflow-x: auto;
|
|
16383
|
+
}
|
|
16384
|
+
|
|
16385
|
+
.gantt-chart {
|
|
16386
|
+
min-width: 600px;
|
|
16387
|
+
}
|
|
16388
|
+
|
|
16389
|
+
.gantt-overlay {
|
|
16390
|
+
position: absolute;
|
|
16391
|
+
top: 0;
|
|
16392
|
+
left: 0;
|
|
16393
|
+
right: 0;
|
|
16394
|
+
bottom: 0;
|
|
16395
|
+
pointer-events: none;
|
|
16396
|
+
display: flex;
|
|
16397
|
+
}
|
|
16398
|
+
|
|
16399
|
+
.gantt-header,
|
|
16400
|
+
.gantt-section-row,
|
|
16401
|
+
.gantt-row,
|
|
16402
|
+
.gantt-overlay {
|
|
16403
|
+
display: flex;
|
|
16404
|
+
align-items: center;
|
|
16405
|
+
}
|
|
16406
|
+
|
|
16407
|
+
.gantt-label {
|
|
16408
|
+
width: 200px;
|
|
16409
|
+
min-width: 200px;
|
|
16410
|
+
padding: 0.3rem 0.75rem;
|
|
16411
|
+
font-size: 0.8rem;
|
|
16412
|
+
color: var(--text-dim);
|
|
16413
|
+
text-align: right;
|
|
16414
|
+
white-space: nowrap;
|
|
16415
|
+
overflow: hidden;
|
|
16416
|
+
text-overflow: ellipsis;
|
|
16417
|
+
}
|
|
16418
|
+
|
|
16419
|
+
.gantt-section-label {
|
|
16420
|
+
font-weight: 600;
|
|
16421
|
+
color: var(--text);
|
|
16422
|
+
font-size: 0.75rem;
|
|
16423
|
+
text-transform: uppercase;
|
|
16424
|
+
letter-spacing: 0.03em;
|
|
16425
|
+
padding-top: 0.6rem;
|
|
16426
|
+
}
|
|
16427
|
+
|
|
16428
|
+
.gantt-track {
|
|
16429
|
+
flex: 1;
|
|
16430
|
+
position: relative;
|
|
16431
|
+
height: 28px;
|
|
16432
|
+
min-width: 0;
|
|
16433
|
+
}
|
|
16434
|
+
|
|
16435
|
+
.gantt-section-row .gantt-track {
|
|
16436
|
+
height: 20px;
|
|
16437
|
+
}
|
|
16438
|
+
|
|
16439
|
+
.gantt-section-bg {
|
|
16440
|
+
position: absolute;
|
|
16441
|
+
top: 0;
|
|
16442
|
+
bottom: 0;
|
|
16443
|
+
background: var(--bg-hover);
|
|
16444
|
+
border-radius: 3px;
|
|
16445
|
+
opacity: 0.4;
|
|
16446
|
+
}
|
|
16447
|
+
|
|
16448
|
+
.gantt-bar {
|
|
16449
|
+
position: absolute;
|
|
16450
|
+
top: 4px;
|
|
16451
|
+
bottom: 4px;
|
|
16452
|
+
border-radius: 4px;
|
|
16453
|
+
min-width: 6px;
|
|
16454
|
+
transition: opacity 0.15s;
|
|
16455
|
+
}
|
|
16456
|
+
|
|
16457
|
+
.gantt-bar:hover {
|
|
16458
|
+
opacity: 0.85;
|
|
16459
|
+
}
|
|
16460
|
+
|
|
16461
|
+
.gantt-bar-done {
|
|
16462
|
+
background: var(--green);
|
|
16463
|
+
}
|
|
16464
|
+
|
|
16465
|
+
.gantt-bar-active {
|
|
16466
|
+
background: var(--amber);
|
|
16467
|
+
}
|
|
16468
|
+
|
|
16469
|
+
.gantt-bar-blocked {
|
|
16470
|
+
background: var(--red);
|
|
16471
|
+
}
|
|
16472
|
+
|
|
16473
|
+
.gantt-bar-default {
|
|
16474
|
+
background: var(--accent-dim);
|
|
16475
|
+
}
|
|
16476
|
+
|
|
16477
|
+
.gantt-dates {
|
|
16478
|
+
height: 24px;
|
|
16479
|
+
border-bottom: 1px solid var(--border);
|
|
16480
|
+
margin-bottom: 0.25rem;
|
|
16481
|
+
}
|
|
16482
|
+
|
|
16483
|
+
.gantt-marker {
|
|
16484
|
+
position: absolute;
|
|
16485
|
+
top: 0;
|
|
16486
|
+
bottom: 0;
|
|
16487
|
+
border-left: 1px solid var(--border);
|
|
16488
|
+
}
|
|
16489
|
+
|
|
16490
|
+
.gantt-marker span {
|
|
16491
|
+
position: absolute;
|
|
16492
|
+
top: 2px;
|
|
16493
|
+
left: 6px;
|
|
16494
|
+
font-size: 0.65rem;
|
|
16495
|
+
color: var(--text-dim);
|
|
16496
|
+
white-space: nowrap;
|
|
16497
|
+
}
|
|
16498
|
+
|
|
16499
|
+
.gantt-today {
|
|
16500
|
+
position: absolute;
|
|
16501
|
+
top: 0;
|
|
16502
|
+
bottom: 0;
|
|
16503
|
+
width: 2px;
|
|
16504
|
+
background: var(--red);
|
|
16505
|
+
opacity: 0.7;
|
|
16506
|
+
}
|
|
16507
|
+
|
|
16508
|
+
/* Pie chart color overrides */
|
|
16509
|
+
.mermaid-container .pieCircle {
|
|
16510
|
+
stroke: var(--bg-card);
|
|
16511
|
+
}
|
|
16512
|
+
|
|
16513
|
+
.mermaid-container text.slice {
|
|
16514
|
+
fill: var(--bg) !important;
|
|
16515
|
+
font-weight: 600;
|
|
16516
|
+
}
|
|
16236
16517
|
`;
|
|
16237
16518
|
}
|
|
16238
16519
|
|
|
@@ -16241,98 +16522,275 @@ function sanitize(text, maxLen = 40) {
|
|
|
16241
16522
|
const cleaned = text.replace(/["'`]/g, "").replace(/[\r\n]+/g, " ");
|
|
16242
16523
|
return cleaned.length > maxLen ? cleaned.slice(0, maxLen - 1) + "\u2026" : cleaned;
|
|
16243
16524
|
}
|
|
16244
|
-
function mermaidBlock(definition) {
|
|
16245
|
-
|
|
16525
|
+
function mermaidBlock(definition, extraClass) {
|
|
16526
|
+
const cls = ["mermaid-container", extraClass].filter(Boolean).join(" ");
|
|
16527
|
+
return `<div class="${cls}"><pre class="mermaid">
|
|
16246
16528
|
${definition}
|
|
16247
16529
|
</pre></div>`;
|
|
16248
16530
|
}
|
|
16249
16531
|
function placeholder(message) {
|
|
16250
16532
|
return `<div class="mermaid-container mermaid-empty"><p>${message}</p></div>`;
|
|
16251
16533
|
}
|
|
16252
|
-
function
|
|
16253
|
-
|
|
16534
|
+
function toMs(date5) {
|
|
16535
|
+
return (/* @__PURE__ */ new Date(date5 + "T00:00:00")).getTime();
|
|
16536
|
+
}
|
|
16537
|
+
function fmtDate(ms) {
|
|
16538
|
+
const d = new Date(ms);
|
|
16539
|
+
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
16540
|
+
return `${months[d.getMonth()]} ${d.getDate()}`;
|
|
16541
|
+
}
|
|
16542
|
+
function buildTimelineGantt(data, maxSprints = 6) {
|
|
16543
|
+
const sprintsWithDates = data.sprints.filter((s) => s.startDate && s.endDate).sort((a, b) => a.startDate < b.startDate ? -1 : 1);
|
|
16254
16544
|
if (sprintsWithDates.length === 0) {
|
|
16255
16545
|
return placeholder("No timeline data available \u2014 sprints need start and end dates.");
|
|
16256
16546
|
}
|
|
16547
|
+
const truncated = sprintsWithDates.length > maxSprints;
|
|
16548
|
+
const visibleSprints = truncated ? sprintsWithDates.slice(-maxSprints) : sprintsWithDates;
|
|
16549
|
+
const hiddenCount = sprintsWithDates.length - visibleSprints.length;
|
|
16257
16550
|
const epicMap = new Map(data.epics.map((e) => [e.id, e]));
|
|
16258
|
-
const
|
|
16259
|
-
|
|
16260
|
-
|
|
16551
|
+
const allStarts = visibleSprints.map((s) => toMs(s.startDate));
|
|
16552
|
+
const allEnds = visibleSprints.map((s) => toMs(s.endDate));
|
|
16553
|
+
const timelineStart = Math.min(...allStarts);
|
|
16554
|
+
const timelineEnd = Math.max(...allEnds);
|
|
16555
|
+
const span = timelineEnd - timelineStart || 1;
|
|
16556
|
+
const pct = (ms) => (ms - timelineStart) / span * 100;
|
|
16557
|
+
const DAY = 864e5;
|
|
16558
|
+
const markers = [];
|
|
16559
|
+
let tick = timelineStart;
|
|
16560
|
+
const startDay = new Date(tick).getDay();
|
|
16561
|
+
tick += (8 - startDay) % 7 * DAY;
|
|
16562
|
+
while (tick <= timelineEnd) {
|
|
16563
|
+
const left = pct(tick);
|
|
16564
|
+
markers.push(
|
|
16565
|
+
`<div class="gantt-marker" style="left:${left.toFixed(2)}%"><span>${fmtDate(tick)}</span></div>`
|
|
16566
|
+
);
|
|
16567
|
+
tick += 7 * DAY;
|
|
16568
|
+
}
|
|
16569
|
+
const now = Date.now();
|
|
16570
|
+
let todayMarker = "";
|
|
16571
|
+
if (now >= timelineStart && now <= timelineEnd) {
|
|
16572
|
+
todayMarker = `<div class="gantt-today" style="left:${pct(now).toFixed(2)}%"></div>`;
|
|
16573
|
+
}
|
|
16574
|
+
const rows = [];
|
|
16575
|
+
for (const sprint of visibleSprints) {
|
|
16576
|
+
const sStart = toMs(sprint.startDate);
|
|
16577
|
+
const sEnd = toMs(sprint.endDate);
|
|
16578
|
+
rows.push(`<div class="gantt-section-row">
|
|
16579
|
+
<div class="gantt-label gantt-section-label">${sanitize(sprint.id + " " + sprint.title, 50)}</div>
|
|
16580
|
+
<div class="gantt-track">
|
|
16581
|
+
<div class="gantt-section-bg" style="left:${pct(sStart).toFixed(2)}%;width:${(pct(sEnd) - pct(sStart)).toFixed(2)}%"></div>
|
|
16582
|
+
</div>
|
|
16583
|
+
</div>`);
|
|
16261
16584
|
const linked = sprint.linkedEpics.map((eid) => epicMap.get(eid)).filter(Boolean);
|
|
16262
|
-
|
|
16263
|
-
|
|
16264
|
-
|
|
16265
|
-
|
|
16266
|
-
|
|
16267
|
-
|
|
16268
|
-
|
|
16585
|
+
const items = linked.length > 0 ? linked.map((e) => ({ label: sanitize(e.id + " " + e.title), status: e.status })) : [{ label: sanitize(sprint.title), status: sprint.status }];
|
|
16586
|
+
for (const item of items) {
|
|
16587
|
+
const cls = item.status === "done" || item.status === "completed" ? "gantt-bar-done" : item.status === "in-progress" || item.status === "active" ? "gantt-bar-active" : item.status === "blocked" ? "gantt-bar-blocked" : "gantt-bar-default";
|
|
16588
|
+
const left = pct(sStart).toFixed(2);
|
|
16589
|
+
const width = (pct(sEnd) - pct(sStart)).toFixed(2);
|
|
16590
|
+
rows.push(`<div class="gantt-row">
|
|
16591
|
+
<div class="gantt-label">${item.label}</div>
|
|
16592
|
+
<div class="gantt-track">
|
|
16593
|
+
<div class="gantt-bar ${cls}" style="left:${left}%;width:${width}%"></div>
|
|
16594
|
+
</div>
|
|
16595
|
+
</div>`);
|
|
16269
16596
|
}
|
|
16270
16597
|
}
|
|
16271
|
-
|
|
16598
|
+
const note = truncated ? `<div class="mermaid-note">${hiddenCount} earlier sprint${hiddenCount > 1 ? "s" : ""} not shown</div>` : "";
|
|
16599
|
+
return `${note}
|
|
16600
|
+
<div class="gantt">
|
|
16601
|
+
<div class="gantt-chart">
|
|
16602
|
+
<div class="gantt-header">
|
|
16603
|
+
<div class="gantt-label"></div>
|
|
16604
|
+
<div class="gantt-track gantt-dates">${markers.join("")}</div>
|
|
16605
|
+
</div>
|
|
16606
|
+
${rows.join("\n")}
|
|
16607
|
+
</div>
|
|
16608
|
+
<div class="gantt-overlay">
|
|
16609
|
+
<div class="gantt-label"></div>
|
|
16610
|
+
<div class="gantt-track">${todayMarker}</div>
|
|
16611
|
+
</div>
|
|
16612
|
+
</div>`;
|
|
16613
|
+
}
|
|
16614
|
+
function statusClass(status) {
|
|
16615
|
+
const s = status.toLowerCase();
|
|
16616
|
+
if (s === "done" || s === "completed") return "flow-done";
|
|
16617
|
+
if (s === "in-progress" || s === "active") return "flow-active";
|
|
16618
|
+
if (s === "blocked") return "flow-blocked";
|
|
16619
|
+
return "flow-default";
|
|
16272
16620
|
}
|
|
16273
16621
|
function buildArtifactFlowchart(data) {
|
|
16274
16622
|
if (data.features.length === 0 && data.epics.length === 0) {
|
|
16275
16623
|
return placeholder("No artifact relationships found \u2014 create features and epics to see the hierarchy.");
|
|
16276
16624
|
}
|
|
16277
|
-
const
|
|
16278
|
-
|
|
16279
|
-
lines.push(" classDef inprogress fill:#78350f,stroke:#fbbf24,color:#fef3c7");
|
|
16280
|
-
lines.push(" classDef blocked fill:#7f1d1d,stroke:#f87171,color:#fee2e2");
|
|
16281
|
-
lines.push(" classDef default fill:#1e293b,stroke:#475569,color:#e2e8f0");
|
|
16282
|
-
const nodeIds = /* @__PURE__ */ new Set();
|
|
16625
|
+
const edges = [];
|
|
16626
|
+
const epicsByFeature = /* @__PURE__ */ new Map();
|
|
16283
16627
|
for (const epic of data.epics) {
|
|
16284
|
-
for (const
|
|
16285
|
-
|
|
16286
|
-
|
|
16287
|
-
|
|
16288
|
-
const eNode = epic.id.replace(/-/g, "_");
|
|
16289
|
-
if (!nodeIds.has(fNode)) {
|
|
16290
|
-
lines.push(` ${fNode}["${sanitize(feature.id + " " + feature.title)}"]`);
|
|
16291
|
-
nodeIds.add(fNode);
|
|
16292
|
-
}
|
|
16293
|
-
if (!nodeIds.has(eNode)) {
|
|
16294
|
-
lines.push(` ${eNode}["${sanitize(epic.id + " " + epic.title)}"]`);
|
|
16295
|
-
nodeIds.add(eNode);
|
|
16296
|
-
}
|
|
16297
|
-
lines.push(` ${fNode} --> ${eNode}`);
|
|
16298
|
-
}
|
|
16628
|
+
for (const fid of epic.linkedFeature) {
|
|
16629
|
+
if (!epicsByFeature.has(fid)) epicsByFeature.set(fid, []);
|
|
16630
|
+
epicsByFeature.get(fid).push(epic.id);
|
|
16631
|
+
edges.push({ from: fid, to: epic.id });
|
|
16299
16632
|
}
|
|
16300
16633
|
}
|
|
16634
|
+
const sprintsByEpic = /* @__PURE__ */ new Map();
|
|
16301
16635
|
for (const sprint of data.sprints) {
|
|
16302
|
-
const
|
|
16303
|
-
|
|
16304
|
-
|
|
16305
|
-
|
|
16306
|
-
|
|
16307
|
-
|
|
16308
|
-
|
|
16309
|
-
|
|
16310
|
-
|
|
16311
|
-
|
|
16312
|
-
|
|
16313
|
-
|
|
16314
|
-
|
|
16315
|
-
|
|
16316
|
-
|
|
16317
|
-
|
|
16318
|
-
|
|
16319
|
-
if (
|
|
16636
|
+
for (const eid of sprint.linkedEpics) {
|
|
16637
|
+
if (!sprintsByEpic.has(eid)) sprintsByEpic.set(eid, []);
|
|
16638
|
+
sprintsByEpic.get(eid).push(sprint.id);
|
|
16639
|
+
edges.push({ from: eid, to: sprint.id });
|
|
16640
|
+
}
|
|
16641
|
+
}
|
|
16642
|
+
const connectedFeatureIds = new Set(epicsByFeature.keys());
|
|
16643
|
+
const connectedEpicIds = /* @__PURE__ */ new Set();
|
|
16644
|
+
for (const ids of epicsByFeature.values()) ids.forEach((id) => connectedEpicIds.add(id));
|
|
16645
|
+
for (const ids of sprintsByEpic.values()) ids.forEach(() => {
|
|
16646
|
+
});
|
|
16647
|
+
for (const eid of sprintsByEpic.keys()) connectedEpicIds.add(eid);
|
|
16648
|
+
const connectedSprintIds = /* @__PURE__ */ new Set();
|
|
16649
|
+
for (const ids of sprintsByEpic.values()) ids.forEach((id) => connectedSprintIds.add(id));
|
|
16650
|
+
const features = data.features.filter((f) => connectedFeatureIds.has(f.id));
|
|
16651
|
+
const epics = data.epics.filter((e) => connectedEpicIds.has(e.id));
|
|
16652
|
+
const sprints = data.sprints.filter((s) => connectedSprintIds.has(s.id)).sort((a, b) => (a.startDate ?? "").localeCompare(b.startDate ?? ""));
|
|
16653
|
+
if (features.length === 0 && epics.length === 0) {
|
|
16320
16654
|
return placeholder("No artifact relationships found \u2014 link epics to features and sprints.");
|
|
16321
16655
|
}
|
|
16322
|
-
const
|
|
16323
|
-
|
|
16324
|
-
|
|
16325
|
-
|
|
16326
|
-
|
|
16327
|
-
|
|
16328
|
-
|
|
16329
|
-
|
|
16330
|
-
|
|
16331
|
-
|
|
16332
|
-
lines
|
|
16333
|
-
|
|
16334
|
-
|
|
16335
|
-
|
|
16656
|
+
const renderNode = (id, title, status, type) => `<div class="flow-node ${statusClass(status)}" data-flow-id="${id}">
|
|
16657
|
+
<a class="flow-node-id" href="/docs/${type}/${id}">${id}</a>
|
|
16658
|
+
<span class="flow-node-title">${sanitize(title, 35)}</span>
|
|
16659
|
+
</div>`;
|
|
16660
|
+
const featuresHtml = features.map((f) => renderNode(f.id, f.title, f.status, "feature")).join("\n");
|
|
16661
|
+
const epicsHtml = epics.map((e) => renderNode(e.id, e.title, e.status, "epic")).join("\n");
|
|
16662
|
+
const sprintsHtml = sprints.map((s) => renderNode(s.id, s.title, s.status, "sprint")).join("\n");
|
|
16663
|
+
const edgesJson = JSON.stringify(edges);
|
|
16664
|
+
return `
|
|
16665
|
+
<div class="flow-diagram" id="flow-diagram">
|
|
16666
|
+
<svg class="flow-lines" id="flow-lines"></svg>
|
|
16667
|
+
<div class="flow-columns">
|
|
16668
|
+
<div class="flow-column">
|
|
16669
|
+
<div class="flow-column-header">Features</div>
|
|
16670
|
+
${featuresHtml}
|
|
16671
|
+
</div>
|
|
16672
|
+
<div class="flow-column">
|
|
16673
|
+
<div class="flow-column-header">Epics</div>
|
|
16674
|
+
${epicsHtml}
|
|
16675
|
+
</div>
|
|
16676
|
+
<div class="flow-column">
|
|
16677
|
+
<div class="flow-column-header">Sprints</div>
|
|
16678
|
+
${sprintsHtml}
|
|
16679
|
+
</div>
|
|
16680
|
+
</div>
|
|
16681
|
+
</div>
|
|
16682
|
+
<script>
|
|
16683
|
+
(function() {
|
|
16684
|
+
var edges = ${edgesJson};
|
|
16685
|
+
var container = document.getElementById('flow-diagram');
|
|
16686
|
+
var svg = document.getElementById('flow-lines');
|
|
16687
|
+
if (!container || !svg) return;
|
|
16688
|
+
|
|
16689
|
+
// Build adjacency map (bidirectional) for traversal
|
|
16690
|
+
var adj = {};
|
|
16691
|
+
edges.forEach(function(e) {
|
|
16692
|
+
if (!adj[e.from]) adj[e.from] = [];
|
|
16693
|
+
if (!adj[e.to]) adj[e.to] = [];
|
|
16694
|
+
adj[e.from].push(e.to);
|
|
16695
|
+
adj[e.to].push(e.from);
|
|
16696
|
+
});
|
|
16697
|
+
|
|
16698
|
+
function drawLines() {
|
|
16699
|
+
var rect = container.getBoundingClientRect();
|
|
16700
|
+
svg.setAttribute('width', rect.width);
|
|
16701
|
+
svg.setAttribute('height', rect.height);
|
|
16702
|
+
svg.innerHTML = '';
|
|
16703
|
+
|
|
16704
|
+
edges.forEach(function(edge) {
|
|
16705
|
+
var fromEl = container.querySelector('[data-flow-id="' + edge.from + '"]');
|
|
16706
|
+
var toEl = container.querySelector('[data-flow-id="' + edge.to + '"]');
|
|
16707
|
+
if (!fromEl || !toEl) return;
|
|
16708
|
+
|
|
16709
|
+
var fr = fromEl.getBoundingClientRect();
|
|
16710
|
+
var tr = toEl.getBoundingClientRect();
|
|
16711
|
+
var x1 = fr.right - rect.left;
|
|
16712
|
+
var y1 = fr.top + fr.height / 2 - rect.top;
|
|
16713
|
+
var x2 = tr.left - rect.left;
|
|
16714
|
+
var y2 = tr.top + tr.height / 2 - rect.top;
|
|
16715
|
+
var mx = (x1 + x2) / 2;
|
|
16716
|
+
|
|
16717
|
+
var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
16718
|
+
path.setAttribute('d', 'M' + x1 + ',' + y1 + ' C' + mx + ',' + y1 + ' ' + mx + ',' + y2 + ' ' + x2 + ',' + y2);
|
|
16719
|
+
path.setAttribute('fill', 'none');
|
|
16720
|
+
path.setAttribute('stroke', '#2a2e3a');
|
|
16721
|
+
path.setAttribute('stroke-width', '1.5');
|
|
16722
|
+
path.dataset.from = edge.from;
|
|
16723
|
+
path.dataset.to = edge.to;
|
|
16724
|
+
svg.appendChild(path);
|
|
16725
|
+
});
|
|
16726
|
+
}
|
|
16727
|
+
|
|
16728
|
+
// Find all nodes reachable from a starting node
|
|
16729
|
+
function findConnected(startId) {
|
|
16730
|
+
var visited = {};
|
|
16731
|
+
var queue = [startId];
|
|
16732
|
+
visited[startId] = true;
|
|
16733
|
+
while (queue.length) {
|
|
16734
|
+
var id = queue.shift();
|
|
16735
|
+
(adj[id] || []).forEach(function(neighbor) {
|
|
16736
|
+
if (!visited[neighbor]) {
|
|
16737
|
+
visited[neighbor] = true;
|
|
16738
|
+
queue.push(neighbor);
|
|
16739
|
+
}
|
|
16740
|
+
});
|
|
16741
|
+
}
|
|
16742
|
+
return visited;
|
|
16743
|
+
}
|
|
16744
|
+
|
|
16745
|
+
function highlight(hoveredId) {
|
|
16746
|
+
var connected = findConnected(hoveredId);
|
|
16747
|
+
container.querySelectorAll('.flow-node').forEach(function(n) {
|
|
16748
|
+
if (connected[n.dataset.flowId]) {
|
|
16749
|
+
n.classList.add('flow-lit');
|
|
16750
|
+
n.classList.remove('flow-dim');
|
|
16751
|
+
} else {
|
|
16752
|
+
n.classList.add('flow-dim');
|
|
16753
|
+
n.classList.remove('flow-lit');
|
|
16754
|
+
}
|
|
16755
|
+
});
|
|
16756
|
+
svg.querySelectorAll('path').forEach(function(p) {
|
|
16757
|
+
if (connected[p.dataset.from] && connected[p.dataset.to]) {
|
|
16758
|
+
p.classList.add('flow-line-lit');
|
|
16759
|
+
p.classList.remove('flow-line-dim');
|
|
16760
|
+
} else {
|
|
16761
|
+
p.classList.add('flow-line-dim');
|
|
16762
|
+
p.classList.remove('flow-line-lit');
|
|
16763
|
+
}
|
|
16764
|
+
});
|
|
16765
|
+
}
|
|
16766
|
+
|
|
16767
|
+
function clearHighlight() {
|
|
16768
|
+
container.querySelectorAll('.flow-node').forEach(function(n) { n.classList.remove('flow-lit', 'flow-dim'); });
|
|
16769
|
+
svg.querySelectorAll('path').forEach(function(p) { p.classList.remove('flow-line-lit', 'flow-line-dim'); });
|
|
16770
|
+
}
|
|
16771
|
+
|
|
16772
|
+
var activeId = null;
|
|
16773
|
+
container.addEventListener('click', function(e) {
|
|
16774
|
+
// Let the ID link navigate normally
|
|
16775
|
+
if (e.target.closest('a')) return;
|
|
16776
|
+
|
|
16777
|
+
var node = e.target.closest('.flow-node');
|
|
16778
|
+
var clickedId = node ? node.dataset.flowId : null;
|
|
16779
|
+
|
|
16780
|
+
if (!clickedId || clickedId === activeId) {
|
|
16781
|
+
activeId = null;
|
|
16782
|
+
clearHighlight();
|
|
16783
|
+
return;
|
|
16784
|
+
}
|
|
16785
|
+
|
|
16786
|
+
activeId = clickedId;
|
|
16787
|
+
highlight(clickedId);
|
|
16788
|
+
});
|
|
16789
|
+
|
|
16790
|
+
requestAnimationFrame(function() { setTimeout(drawLines, 100); });
|
|
16791
|
+
window.addEventListener('resize', drawLines);
|
|
16792
|
+
})();
|
|
16793
|
+
</script>`;
|
|
16336
16794
|
}
|
|
16337
16795
|
function buildStatusPie(title, counts) {
|
|
16338
16796
|
const entries = Object.entries(counts).filter(([, v]) => v > 0);
|
|
@@ -16412,8 +16870,7 @@ function overviewPage(data, diagrams, navGroups) {
|
|
|
16412
16870
|
${groupSections}
|
|
16413
16871
|
${ungroupedSection}
|
|
16414
16872
|
|
|
16415
|
-
<div class="section-title">Project Timeline
|
|
16416
|
-
${buildTimelineGantt(diagrams)}
|
|
16873
|
+
<div class="section-title"><a href="/timeline">Project Timeline →</a></div>
|
|
16417
16874
|
|
|
16418
16875
|
<div class="section-title">Artifact Relationships</div>
|
|
16419
16876
|
${buildArtifactFlowchart(diagrams)}
|
|
@@ -16668,6 +17125,7 @@ function boardPage(data) {
|
|
|
16668
17125
|
<span>${escapeHtml(col.status)}</span>
|
|
16669
17126
|
<span class="count">${col.docs.length}</span>
|
|
16670
17127
|
</div>
|
|
17128
|
+
<div class="board-column-cards">
|
|
16671
17129
|
${col.docs.map(
|
|
16672
17130
|
(doc) => `
|
|
16673
17131
|
<div class="board-card">
|
|
@@ -16678,6 +17136,7 @@ function boardPage(data) {
|
|
|
16678
17136
|
</a>
|
|
16679
17137
|
</div>`
|
|
16680
17138
|
).join("\n")}
|
|
17139
|
+
</div>
|
|
16681
17140
|
</div>`
|
|
16682
17141
|
).join("\n");
|
|
16683
17142
|
return `
|
|
@@ -16703,6 +17162,18 @@ function boardPage(data) {
|
|
|
16703
17162
|
`;
|
|
16704
17163
|
}
|
|
16705
17164
|
|
|
17165
|
+
// src/web/templates/pages/timeline.ts
|
|
17166
|
+
function timelinePage(diagrams) {
|
|
17167
|
+
return `
|
|
17168
|
+
<div class="page-header">
|
|
17169
|
+
<h2>Project Timeline</h2>
|
|
17170
|
+
<div class="subtitle">Sprint schedule with linked epics</div>
|
|
17171
|
+
</div>
|
|
17172
|
+
|
|
17173
|
+
${buildTimelineGantt(diagrams)}
|
|
17174
|
+
`;
|
|
17175
|
+
}
|
|
17176
|
+
|
|
16706
17177
|
// src/web/router.ts
|
|
16707
17178
|
function handleRequest(req, res, store, projectName, navGroups) {
|
|
16708
17179
|
const parsed = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
@@ -16724,6 +17195,12 @@ function handleRequest(req, res, store, projectName, navGroups) {
|
|
|
16724
17195
|
respond(res, layout({ title: "Overview", activePath: "/", projectName, navGroups }, body));
|
|
16725
17196
|
return;
|
|
16726
17197
|
}
|
|
17198
|
+
if (pathname === "/timeline") {
|
|
17199
|
+
const diagrams = getDiagramData(store);
|
|
17200
|
+
const body = timelinePage(diagrams);
|
|
17201
|
+
respond(res, layout({ title: "Timeline", activePath: "/timeline", projectName, navGroups, mainClass: "expanded" }, body));
|
|
17202
|
+
return;
|
|
17203
|
+
}
|
|
16727
17204
|
if (pathname === "/gar") {
|
|
16728
17205
|
const report = getGarData(store, projectName);
|
|
16729
17206
|
const body = garPage(report);
|
|
@@ -18107,6 +18584,205 @@ function createSprintPlanningTools(store) {
|
|
|
18107
18584
|
];
|
|
18108
18585
|
}
|
|
18109
18586
|
|
|
18587
|
+
// src/plugins/builtin/tools/tasks.ts
|
|
18588
|
+
import { tool as tool14 } from "@anthropic-ai/claude-agent-sdk";
|
|
18589
|
+
|
|
18590
|
+
// src/plugins/builtin/tools/task-utils.ts
|
|
18591
|
+
function normalizeLinkedEpics(value) {
|
|
18592
|
+
if (value === void 0 || value === null) return [];
|
|
18593
|
+
if (typeof value === "string") {
|
|
18594
|
+
try {
|
|
18595
|
+
const parsed = JSON.parse(value);
|
|
18596
|
+
if (Array.isArray(parsed)) return parsed.filter((v) => typeof v === "string");
|
|
18597
|
+
} catch {
|
|
18598
|
+
}
|
|
18599
|
+
return [value];
|
|
18600
|
+
}
|
|
18601
|
+
if (Array.isArray(value)) return value.filter((v) => typeof v === "string");
|
|
18602
|
+
return [];
|
|
18603
|
+
}
|
|
18604
|
+
function generateEpicTags(epics) {
|
|
18605
|
+
return epics.map((id) => `epic:${id}`);
|
|
18606
|
+
}
|
|
18607
|
+
|
|
18608
|
+
// src/plugins/builtin/tools/tasks.ts
|
|
18609
|
+
var linkedEpicArray = external_exports.preprocess(
|
|
18610
|
+
(val) => {
|
|
18611
|
+
if (typeof val === "string") {
|
|
18612
|
+
try {
|
|
18613
|
+
const parsed = JSON.parse(val);
|
|
18614
|
+
if (Array.isArray(parsed)) return parsed;
|
|
18615
|
+
} catch {
|
|
18616
|
+
}
|
|
18617
|
+
return [val];
|
|
18618
|
+
}
|
|
18619
|
+
return val;
|
|
18620
|
+
},
|
|
18621
|
+
external_exports.array(external_exports.string())
|
|
18622
|
+
);
|
|
18623
|
+
function createTaskTools(store) {
|
|
18624
|
+
return [
|
|
18625
|
+
tool14(
|
|
18626
|
+
"list_tasks",
|
|
18627
|
+
"List all tasks in the project, optionally filtered by status, linked epic, or priority",
|
|
18628
|
+
{
|
|
18629
|
+
status: external_exports.enum(["backlog", "ready", "in-progress", "review", "done"]).optional().describe("Filter by task status"),
|
|
18630
|
+
linkedEpic: external_exports.string().optional().describe("Filter by linked epic ID (e.g. 'E-001')"),
|
|
18631
|
+
priority: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("Filter by priority")
|
|
18632
|
+
},
|
|
18633
|
+
async (args) => {
|
|
18634
|
+
let docs = store.list({ type: "task", status: args.status });
|
|
18635
|
+
if (args.linkedEpic) {
|
|
18636
|
+
docs = docs.filter(
|
|
18637
|
+
(d) => normalizeLinkedEpics(d.frontmatter.linkedEpic).includes(args.linkedEpic)
|
|
18638
|
+
);
|
|
18639
|
+
}
|
|
18640
|
+
if (args.priority) {
|
|
18641
|
+
docs = docs.filter((d) => d.frontmatter.priority === args.priority);
|
|
18642
|
+
}
|
|
18643
|
+
const summary = docs.map((d) => ({
|
|
18644
|
+
id: d.frontmatter.id,
|
|
18645
|
+
title: d.frontmatter.title,
|
|
18646
|
+
status: d.frontmatter.status,
|
|
18647
|
+
linkedEpic: normalizeLinkedEpics(d.frontmatter.linkedEpic),
|
|
18648
|
+
priority: d.frontmatter.priority,
|
|
18649
|
+
complexity: d.frontmatter.complexity,
|
|
18650
|
+
estimatedPoints: d.frontmatter.estimatedPoints,
|
|
18651
|
+
tags: d.frontmatter.tags
|
|
18652
|
+
}));
|
|
18653
|
+
return {
|
|
18654
|
+
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
18655
|
+
};
|
|
18656
|
+
},
|
|
18657
|
+
{ annotations: { readOnlyHint: true } }
|
|
18658
|
+
),
|
|
18659
|
+
tool14(
|
|
18660
|
+
"get_task",
|
|
18661
|
+
"Get the full content of a specific task by ID",
|
|
18662
|
+
{ id: external_exports.string().describe("Task ID (e.g. 'T-001')") },
|
|
18663
|
+
async (args) => {
|
|
18664
|
+
const doc = store.get(args.id);
|
|
18665
|
+
if (!doc) {
|
|
18666
|
+
return {
|
|
18667
|
+
content: [{ type: "text", text: `Task ${args.id} not found` }],
|
|
18668
|
+
isError: true
|
|
18669
|
+
};
|
|
18670
|
+
}
|
|
18671
|
+
return {
|
|
18672
|
+
content: [
|
|
18673
|
+
{
|
|
18674
|
+
type: "text",
|
|
18675
|
+
text: JSON.stringify(
|
|
18676
|
+
{ ...doc.frontmatter, content: doc.content },
|
|
18677
|
+
null,
|
|
18678
|
+
2
|
|
18679
|
+
)
|
|
18680
|
+
}
|
|
18681
|
+
]
|
|
18682
|
+
};
|
|
18683
|
+
},
|
|
18684
|
+
{ annotations: { readOnlyHint: true } }
|
|
18685
|
+
),
|
|
18686
|
+
tool14(
|
|
18687
|
+
"create_task",
|
|
18688
|
+
"Create a new implementation task linked to one or more epics. The linked epic is soft-validated (warns if not found, but does not block creation).",
|
|
18689
|
+
{
|
|
18690
|
+
title: external_exports.string().describe("Task title"),
|
|
18691
|
+
content: external_exports.string().describe("Task description and implementation details"),
|
|
18692
|
+
linkedEpic: linkedEpicArray.describe("Epic ID(s) to link this task to (e.g. ['E-001'] or ['E-001', 'E-002'])"),
|
|
18693
|
+
status: external_exports.enum(["backlog", "ready", "in-progress", "review", "done"]).optional().describe("Task status (default: 'backlog')"),
|
|
18694
|
+
acceptanceCriteria: external_exports.string().optional().describe("Acceptance criteria for the task"),
|
|
18695
|
+
technicalNotes: external_exports.string().optional().describe("Technical implementation notes"),
|
|
18696
|
+
estimatedPoints: external_exports.number().optional().describe("Story point estimate"),
|
|
18697
|
+
complexity: external_exports.enum(["trivial", "simple", "moderate", "complex", "very-complex"]).optional().describe("Task complexity"),
|
|
18698
|
+
priority: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("Task priority"),
|
|
18699
|
+
tags: external_exports.array(external_exports.string()).optional().describe("Additional tags")
|
|
18700
|
+
},
|
|
18701
|
+
async (args) => {
|
|
18702
|
+
const linkedEpics = normalizeLinkedEpics(args.linkedEpic);
|
|
18703
|
+
const warnings = [];
|
|
18704
|
+
for (const epicId of linkedEpics) {
|
|
18705
|
+
const epic = store.get(epicId);
|
|
18706
|
+
if (!epic) {
|
|
18707
|
+
warnings.push(`Warning: Epic ${epicId} not found`);
|
|
18708
|
+
} else if (epic.frontmatter.type !== "epic") {
|
|
18709
|
+
warnings.push(`Warning: ${epicId} is a ${epic.frontmatter.type}, not an epic`);
|
|
18710
|
+
}
|
|
18711
|
+
}
|
|
18712
|
+
const frontmatter = {
|
|
18713
|
+
title: args.title,
|
|
18714
|
+
status: args.status ?? "backlog",
|
|
18715
|
+
linkedEpic: linkedEpics,
|
|
18716
|
+
tags: [...generateEpicTags(linkedEpics), ...args.tags ?? []]
|
|
18717
|
+
};
|
|
18718
|
+
if (args.acceptanceCriteria) frontmatter.acceptanceCriteria = args.acceptanceCriteria;
|
|
18719
|
+
if (args.technicalNotes) frontmatter.technicalNotes = args.technicalNotes;
|
|
18720
|
+
if (args.estimatedPoints !== void 0) frontmatter.estimatedPoints = args.estimatedPoints;
|
|
18721
|
+
if (args.complexity) frontmatter.complexity = args.complexity;
|
|
18722
|
+
if (args.priority) frontmatter.priority = args.priority;
|
|
18723
|
+
const doc = store.create("task", frontmatter, args.content);
|
|
18724
|
+
const parts = [
|
|
18725
|
+
`Created task ${doc.frontmatter.id}: ${doc.frontmatter.title} (linked to ${linkedEpics.join(", ")})`
|
|
18726
|
+
];
|
|
18727
|
+
if (warnings.length > 0) {
|
|
18728
|
+
parts.push(warnings.join("; "));
|
|
18729
|
+
}
|
|
18730
|
+
return {
|
|
18731
|
+
content: [{ type: "text", text: parts.join("\n") }]
|
|
18732
|
+
};
|
|
18733
|
+
}
|
|
18734
|
+
),
|
|
18735
|
+
tool14(
|
|
18736
|
+
"update_task",
|
|
18737
|
+
"Update an existing task, including its linked epics.",
|
|
18738
|
+
{
|
|
18739
|
+
id: external_exports.string().describe("Task ID to update"),
|
|
18740
|
+
title: external_exports.string().optional().describe("New title"),
|
|
18741
|
+
status: external_exports.enum(["backlog", "ready", "in-progress", "review", "done"]).optional().describe("New status"),
|
|
18742
|
+
content: external_exports.string().optional().describe("New content"),
|
|
18743
|
+
linkedEpic: linkedEpicArray.optional().describe("New linked epic ID(s)"),
|
|
18744
|
+
acceptanceCriteria: external_exports.string().optional().describe("New acceptance criteria"),
|
|
18745
|
+
technicalNotes: external_exports.string().optional().describe("New technical notes"),
|
|
18746
|
+
estimatedPoints: external_exports.number().optional().describe("New story point estimate"),
|
|
18747
|
+
complexity: external_exports.enum(["trivial", "simple", "moderate", "complex", "very-complex"]).optional().describe("New complexity"),
|
|
18748
|
+
priority: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("New priority"),
|
|
18749
|
+
tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove old tags, add new ones)")
|
|
18750
|
+
},
|
|
18751
|
+
async (args) => {
|
|
18752
|
+
const { id, content, linkedEpic: rawLinkedEpic, tags: userTags, ...updates } = args;
|
|
18753
|
+
const warnings = [];
|
|
18754
|
+
if (rawLinkedEpic !== void 0) {
|
|
18755
|
+
const linkedEpics = normalizeLinkedEpics(rawLinkedEpic);
|
|
18756
|
+
for (const epicId of linkedEpics) {
|
|
18757
|
+
const epic = store.get(epicId);
|
|
18758
|
+
if (!epic) {
|
|
18759
|
+
warnings.push(`Warning: Epic ${epicId} not found`);
|
|
18760
|
+
} else if (epic.frontmatter.type !== "epic") {
|
|
18761
|
+
warnings.push(`Warning: ${epicId} is a ${epic.frontmatter.type}, not an epic`);
|
|
18762
|
+
}
|
|
18763
|
+
}
|
|
18764
|
+
updates.linkedEpic = linkedEpics;
|
|
18765
|
+
const existingDoc = store.get(id);
|
|
18766
|
+
const existingTags = existingDoc?.frontmatter.tags ?? [];
|
|
18767
|
+
const nonEpicTags = existingTags.filter((t) => !t.startsWith("epic:"));
|
|
18768
|
+
const baseTags = userTags ?? nonEpicTags;
|
|
18769
|
+
updates.tags = [...generateEpicTags(linkedEpics), ...baseTags];
|
|
18770
|
+
} else if (userTags !== void 0) {
|
|
18771
|
+
updates.tags = userTags;
|
|
18772
|
+
}
|
|
18773
|
+
const doc = store.update(id, updates, content);
|
|
18774
|
+
const parts = [`Updated task ${doc.frontmatter.id}: ${doc.frontmatter.title}`];
|
|
18775
|
+
if (warnings.length > 0) {
|
|
18776
|
+
parts.push(warnings.join("; "));
|
|
18777
|
+
}
|
|
18778
|
+
return {
|
|
18779
|
+
content: [{ type: "text", text: parts.join("\n") }]
|
|
18780
|
+
};
|
|
18781
|
+
}
|
|
18782
|
+
)
|
|
18783
|
+
];
|
|
18784
|
+
}
|
|
18785
|
+
|
|
18110
18786
|
// src/plugins/common.ts
|
|
18111
18787
|
var COMMON_REGISTRATIONS = [
|
|
18112
18788
|
{ type: "meeting", dirName: "meetings", idPrefix: "M" },
|
|
@@ -18114,7 +18790,8 @@ var COMMON_REGISTRATIONS = [
|
|
|
18114
18790
|
{ type: "feature", dirName: "features", idPrefix: "F" },
|
|
18115
18791
|
{ type: "epic", dirName: "epics", idPrefix: "E" },
|
|
18116
18792
|
{ type: "contribution", dirName: "contributions", idPrefix: "C" },
|
|
18117
|
-
{ type: "sprint", dirName: "sprints", idPrefix: "SP" }
|
|
18793
|
+
{ type: "sprint", dirName: "sprints", idPrefix: "SP" },
|
|
18794
|
+
{ type: "task", dirName: "tasks", idPrefix: "T" }
|
|
18118
18795
|
];
|
|
18119
18796
|
function createCommonTools(store) {
|
|
18120
18797
|
return [
|
|
@@ -18124,7 +18801,8 @@ function createCommonTools(store) {
|
|
|
18124
18801
|
...createEpicTools(store),
|
|
18125
18802
|
...createContributionTools(store),
|
|
18126
18803
|
...createSprintTools(store),
|
|
18127
|
-
...createSprintPlanningTools(store)
|
|
18804
|
+
...createSprintPlanningTools(store),
|
|
18805
|
+
...createTaskTools(store)
|
|
18128
18806
|
];
|
|
18129
18807
|
}
|
|
18130
18808
|
|
|
@@ -18134,7 +18812,7 @@ var genericAgilePlugin = {
|
|
|
18134
18812
|
name: "Generic Agile",
|
|
18135
18813
|
description: "Default methodology plugin providing standard agile governance patterns for decisions, actions, and questions.",
|
|
18136
18814
|
version: "0.1.0",
|
|
18137
|
-
documentTypes: ["decision", "action", "question", "meeting", "report", "feature", "epic", "contribution", "sprint"],
|
|
18815
|
+
documentTypes: ["decision", "action", "question", "meeting", "report", "feature", "epic", "contribution", "sprint", "task"],
|
|
18138
18816
|
documentTypeRegistrations: [...COMMON_REGISTRATIONS],
|
|
18139
18817
|
tools: (store) => [...createCommonTools(store)],
|
|
18140
18818
|
promptFragments: {
|
|
@@ -18173,6 +18851,11 @@ var genericAgilePlugin = {
|
|
|
18173
18851
|
- **create_epic**: Create implementation epics linked to approved features. The system enforces that the linked feature must exist and be approved \u2014 if it's still "draft", ask the Product Owner to approve it first.
|
|
18174
18852
|
- **update_epic**: Update epic status (planned \u2192 in-progress \u2192 done), owner, and other fields.
|
|
18175
18853
|
|
|
18854
|
+
**Task Tools:**
|
|
18855
|
+
- **list_tasks** / **get_task**: Browse and read implementation tasks.
|
|
18856
|
+
- **create_task**: Create implementation tasks linked to epics. Linked epics are soft-validated (warns if not found, does not block). Tasks auto-generate \`epic:E-xxx\` tags. Default status: "backlog".
|
|
18857
|
+
- **update_task**: Update task status (backlog \u2192 ready \u2192 in-progress \u2192 review \u2192 done), acceptance criteria, technical notes, complexity, priority, and estimated points.
|
|
18858
|
+
|
|
18176
18859
|
**Feature Tools (read-only for awareness):**
|
|
18177
18860
|
- **list_features** / **get_feature**: View features to understand what needs to be broken into epics.
|
|
18178
18861
|
|
|
@@ -18184,6 +18867,7 @@ var genericAgilePlugin = {
|
|
|
18184
18867
|
|
|
18185
18868
|
**Key Workflow Rules:**
|
|
18186
18869
|
- Only create epics against approved features \u2014 create_epic enforces this.
|
|
18870
|
+
- Break epics into tasks (T-xxx) with clear acceptance criteria and complexity estimates.
|
|
18187
18871
|
- Tag work items (actions, decisions, questions) with \`epic:E-xxx\` to group them under an epic.
|
|
18188
18872
|
- Collaborate with the Delivery Manager on target dates and effort estimates.
|
|
18189
18873
|
- Each epic should have a clear scope and definition of done.
|
|
@@ -18219,6 +18903,9 @@ var genericAgilePlugin = {
|
|
|
18219
18903
|
- **list_epics** / **get_epic**: View epics and their current status.
|
|
18220
18904
|
- **update_epic**: Set targetDate and estimatedEffort on epics. Flag epics linked to deferred features.
|
|
18221
18905
|
|
|
18906
|
+
**Task Tools (read-only for tracking):**
|
|
18907
|
+
- **list_tasks** / **get_task**: View tasks and their statuses. Filter by linkedEpic to see implementation breakdown.
|
|
18908
|
+
|
|
18222
18909
|
**Feature Tools (tracking focus):**
|
|
18223
18910
|
- **list_features** / **get_feature**: View features and their priorities.
|
|
18224
18911
|
|
|
@@ -18264,14 +18951,15 @@ var genericAgilePlugin = {
|
|
|
18264
18951
|
- Reason through: priority (critical/high features first), capacity (compare backlog effort to velocity reference), dependencies and blockers, balance across features, and risk.
|
|
18265
18952
|
- Present a structured sprint proposal: title, goal, suggested dates, selected epics with rationale for each, excluded epics with reason, and identified risks.
|
|
18266
18953
|
- After user confirmation, use **create_sprint** with the agreed epics to persist the sprint.`,
|
|
18267
|
-
"*": `You have access to feature, epic, sprint, and meeting tools for project coordination:
|
|
18954
|
+
"*": `You have access to feature, epic, task, sprint, and meeting tools for project coordination:
|
|
18268
18955
|
|
|
18269
18956
|
**Features** (F-xxx): Product capabilities defined by the Product Owner. Features progress through draft \u2192 approved \u2192 done.
|
|
18270
18957
|
**Epics** (E-xxx): Implementation work packages created by the Tech Lead, linked to approved features. Epics progress through planned \u2192 in-progress \u2192 done.
|
|
18958
|
+
**Tasks** (T-xxx): Concrete implementation items created by the Tech Lead, linked to epics. Tasks progress through backlog \u2192 ready \u2192 in-progress \u2192 review \u2192 done.
|
|
18271
18959
|
**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).
|
|
18272
18960
|
**Meetings**: Meeting records with attendees, agendas, and notes.
|
|
18273
18961
|
|
|
18274
|
-
**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.
|
|
18962
|
+
**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 and tasks, 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.
|
|
18275
18963
|
|
|
18276
18964
|
- **list_meetings** / **get_meeting**: Browse and read meeting records.
|
|
18277
18965
|
- **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.
|
|
@@ -18286,10 +18974,10 @@ var genericAgilePlugin = {
|
|
|
18286
18974
|
};
|
|
18287
18975
|
|
|
18288
18976
|
// src/plugins/builtin/tools/use-cases.ts
|
|
18289
|
-
import { tool as
|
|
18977
|
+
import { tool as tool15 } from "@anthropic-ai/claude-agent-sdk";
|
|
18290
18978
|
function createUseCaseTools(store) {
|
|
18291
18979
|
return [
|
|
18292
|
-
|
|
18980
|
+
tool15(
|
|
18293
18981
|
"list_use_cases",
|
|
18294
18982
|
"List all extension use cases, optionally filtered by status or extension type",
|
|
18295
18983
|
{
|
|
@@ -18319,7 +19007,7 @@ function createUseCaseTools(store) {
|
|
|
18319
19007
|
},
|
|
18320
19008
|
{ annotations: { readOnlyHint: true } }
|
|
18321
19009
|
),
|
|
18322
|
-
|
|
19010
|
+
tool15(
|
|
18323
19011
|
"get_use_case",
|
|
18324
19012
|
"Get the full content of a specific use case by ID",
|
|
18325
19013
|
{ id: external_exports.string().describe("Use case ID (e.g. 'UC-001')") },
|
|
@@ -18346,7 +19034,7 @@ function createUseCaseTools(store) {
|
|
|
18346
19034
|
},
|
|
18347
19035
|
{ annotations: { readOnlyHint: true } }
|
|
18348
19036
|
),
|
|
18349
|
-
|
|
19037
|
+
tool15(
|
|
18350
19038
|
"create_use_case",
|
|
18351
19039
|
"Create a new extension use case definition (Phase 1: Assess Extension Use Case)",
|
|
18352
19040
|
{
|
|
@@ -18380,7 +19068,7 @@ function createUseCaseTools(store) {
|
|
|
18380
19068
|
};
|
|
18381
19069
|
}
|
|
18382
19070
|
),
|
|
18383
|
-
|
|
19071
|
+
tool15(
|
|
18384
19072
|
"update_use_case",
|
|
18385
19073
|
"Update an existing extension use case",
|
|
18386
19074
|
{
|
|
@@ -18410,10 +19098,10 @@ function createUseCaseTools(store) {
|
|
|
18410
19098
|
}
|
|
18411
19099
|
|
|
18412
19100
|
// src/plugins/builtin/tools/tech-assessments.ts
|
|
18413
|
-
import { tool as
|
|
19101
|
+
import { tool as tool16 } from "@anthropic-ai/claude-agent-sdk";
|
|
18414
19102
|
function createTechAssessmentTools(store) {
|
|
18415
19103
|
return [
|
|
18416
|
-
|
|
19104
|
+
tool16(
|
|
18417
19105
|
"list_tech_assessments",
|
|
18418
19106
|
"List all technology assessments, optionally filtered by status",
|
|
18419
19107
|
{
|
|
@@ -18444,7 +19132,7 @@ function createTechAssessmentTools(store) {
|
|
|
18444
19132
|
},
|
|
18445
19133
|
{ annotations: { readOnlyHint: true } }
|
|
18446
19134
|
),
|
|
18447
|
-
|
|
19135
|
+
tool16(
|
|
18448
19136
|
"get_tech_assessment",
|
|
18449
19137
|
"Get the full content of a specific technology assessment by ID",
|
|
18450
19138
|
{ id: external_exports.string().describe("Tech assessment ID (e.g. 'TA-001')") },
|
|
@@ -18471,7 +19159,7 @@ function createTechAssessmentTools(store) {
|
|
|
18471
19159
|
},
|
|
18472
19160
|
{ annotations: { readOnlyHint: true } }
|
|
18473
19161
|
),
|
|
18474
|
-
|
|
19162
|
+
tool16(
|
|
18475
19163
|
"create_tech_assessment",
|
|
18476
19164
|
"Create a new technology assessment linked to an assessed/approved use case (Phase 2: Assess Extension Technology)",
|
|
18477
19165
|
{
|
|
@@ -18542,7 +19230,7 @@ function createTechAssessmentTools(store) {
|
|
|
18542
19230
|
};
|
|
18543
19231
|
}
|
|
18544
19232
|
),
|
|
18545
|
-
|
|
19233
|
+
tool16(
|
|
18546
19234
|
"update_tech_assessment",
|
|
18547
19235
|
"Update an existing technology assessment. The linked use case cannot be changed.",
|
|
18548
19236
|
{
|
|
@@ -18572,10 +19260,10 @@ function createTechAssessmentTools(store) {
|
|
|
18572
19260
|
}
|
|
18573
19261
|
|
|
18574
19262
|
// src/plugins/builtin/tools/extension-designs.ts
|
|
18575
|
-
import { tool as
|
|
19263
|
+
import { tool as tool17 } from "@anthropic-ai/claude-agent-sdk";
|
|
18576
19264
|
function createExtensionDesignTools(store) {
|
|
18577
19265
|
return [
|
|
18578
|
-
|
|
19266
|
+
tool17(
|
|
18579
19267
|
"list_extension_designs",
|
|
18580
19268
|
"List all extension designs, optionally filtered by status",
|
|
18581
19269
|
{
|
|
@@ -18605,7 +19293,7 @@ function createExtensionDesignTools(store) {
|
|
|
18605
19293
|
},
|
|
18606
19294
|
{ annotations: { readOnlyHint: true } }
|
|
18607
19295
|
),
|
|
18608
|
-
|
|
19296
|
+
tool17(
|
|
18609
19297
|
"get_extension_design",
|
|
18610
19298
|
"Get the full content of a specific extension design by ID",
|
|
18611
19299
|
{ id: external_exports.string().describe("Extension design ID (e.g. 'XD-001')") },
|
|
@@ -18632,7 +19320,7 @@ function createExtensionDesignTools(store) {
|
|
|
18632
19320
|
},
|
|
18633
19321
|
{ annotations: { readOnlyHint: true } }
|
|
18634
19322
|
),
|
|
18635
|
-
|
|
19323
|
+
tool17(
|
|
18636
19324
|
"create_extension_design",
|
|
18637
19325
|
"Create a new extension design linked to a recommended tech assessment (Phase 3: Define Extension Target Solution)",
|
|
18638
19326
|
{
|
|
@@ -18700,7 +19388,7 @@ function createExtensionDesignTools(store) {
|
|
|
18700
19388
|
};
|
|
18701
19389
|
}
|
|
18702
19390
|
),
|
|
18703
|
-
|
|
19391
|
+
tool17(
|
|
18704
19392
|
"update_extension_design",
|
|
18705
19393
|
"Update an existing extension design. The linked tech assessment cannot be changed.",
|
|
18706
19394
|
{
|
|
@@ -18729,10 +19417,10 @@ function createExtensionDesignTools(store) {
|
|
|
18729
19417
|
}
|
|
18730
19418
|
|
|
18731
19419
|
// src/plugins/builtin/tools/aem-reports.ts
|
|
18732
|
-
import { tool as
|
|
19420
|
+
import { tool as tool18 } from "@anthropic-ai/claude-agent-sdk";
|
|
18733
19421
|
function createAemReportTools(store) {
|
|
18734
19422
|
return [
|
|
18735
|
-
|
|
19423
|
+
tool18(
|
|
18736
19424
|
"generate_extension_portfolio",
|
|
18737
19425
|
"Generate a portfolio view of all use cases with their linked tech assessments and extension designs",
|
|
18738
19426
|
{},
|
|
@@ -18784,7 +19472,7 @@ function createAemReportTools(store) {
|
|
|
18784
19472
|
},
|
|
18785
19473
|
{ annotations: { readOnlyHint: true } }
|
|
18786
19474
|
),
|
|
18787
|
-
|
|
19475
|
+
tool18(
|
|
18788
19476
|
"generate_tech_readiness",
|
|
18789
19477
|
"Generate a BTP technology readiness report showing service coverage and gaps across assessments",
|
|
18790
19478
|
{},
|
|
@@ -18836,7 +19524,7 @@ function createAemReportTools(store) {
|
|
|
18836
19524
|
},
|
|
18837
19525
|
{ annotations: { readOnlyHint: true } }
|
|
18838
19526
|
),
|
|
18839
|
-
|
|
19527
|
+
tool18(
|
|
18840
19528
|
"generate_phase_status",
|
|
18841
19529
|
"Generate a phase progress report showing artifact counts and readiness per AEM phase",
|
|
18842
19530
|
{},
|
|
@@ -18898,11 +19586,11 @@ function createAemReportTools(store) {
|
|
|
18898
19586
|
import * as fs5 from "fs";
|
|
18899
19587
|
import * as path5 from "path";
|
|
18900
19588
|
import * as YAML2 from "yaml";
|
|
18901
|
-
import { tool as
|
|
19589
|
+
import { tool as tool19 } from "@anthropic-ai/claude-agent-sdk";
|
|
18902
19590
|
var PHASES = ["assess-use-case", "assess-technology", "define-solution"];
|
|
18903
19591
|
function createAemPhaseTools(store, marvinDir) {
|
|
18904
19592
|
return [
|
|
18905
|
-
|
|
19593
|
+
tool19(
|
|
18906
19594
|
"get_current_phase",
|
|
18907
19595
|
"Get the current AEM phase from project configuration",
|
|
18908
19596
|
{},
|
|
@@ -18923,7 +19611,7 @@ function createAemPhaseTools(store, marvinDir) {
|
|
|
18923
19611
|
},
|
|
18924
19612
|
{ annotations: { readOnlyHint: true } }
|
|
18925
19613
|
),
|
|
18926
|
-
|
|
19614
|
+
tool19(
|
|
18927
19615
|
"advance_phase",
|
|
18928
19616
|
"Advance to the next AEM phase. Performs soft gate checks and warns if artifacts are incomplete, but does not block.",
|
|
18929
19617
|
{
|
|
@@ -19187,8 +19875,8 @@ function getPluginPromptFragment(plugin, personaId) {
|
|
|
19187
19875
|
}
|
|
19188
19876
|
|
|
19189
19877
|
// src/skills/registry.ts
|
|
19190
|
-
import * as
|
|
19191
|
-
import * as
|
|
19878
|
+
import * as fs7 from "fs";
|
|
19879
|
+
import * as path7 from "path";
|
|
19192
19880
|
import { fileURLToPath } from "url";
|
|
19193
19881
|
import * as YAML3 from "yaml";
|
|
19194
19882
|
import matter2 from "gray-matter";
|
|
@@ -19231,7 +19919,7 @@ Be thorough but concise. Focus on actionable insights.`,
|
|
|
19231
19919
|
};
|
|
19232
19920
|
|
|
19233
19921
|
// src/skills/builtin/jira/tools.ts
|
|
19234
|
-
import { tool as
|
|
19922
|
+
import { tool as tool20 } from "@anthropic-ai/claude-agent-sdk";
|
|
19235
19923
|
|
|
19236
19924
|
// src/skills/builtin/jira/client.ts
|
|
19237
19925
|
var JiraClient = class {
|
|
@@ -19241,8 +19929,8 @@ var JiraClient = class {
|
|
|
19241
19929
|
this.baseUrl = `https://${config2.host}/rest/api/2`;
|
|
19242
19930
|
this.authHeader = "Basic " + Buffer.from(`${config2.email}:${config2.apiToken}`).toString("base64");
|
|
19243
19931
|
}
|
|
19244
|
-
async request(
|
|
19245
|
-
const url2 = `${this.baseUrl}${
|
|
19932
|
+
async request(path21, method = "GET", body) {
|
|
19933
|
+
const url2 = `${this.baseUrl}${path21}`;
|
|
19246
19934
|
const headers = {
|
|
19247
19935
|
Authorization: this.authHeader,
|
|
19248
19936
|
"Content-Type": "application/json",
|
|
@@ -19256,7 +19944,7 @@ var JiraClient = class {
|
|
|
19256
19944
|
if (!response.ok) {
|
|
19257
19945
|
const text = await response.text().catch(() => "");
|
|
19258
19946
|
throw new Error(
|
|
19259
|
-
`Jira API error ${response.status} ${method} ${
|
|
19947
|
+
`Jira API error ${response.status} ${method} ${path21}: ${text}`
|
|
19260
19948
|
);
|
|
19261
19949
|
}
|
|
19262
19950
|
if (response.status === 204) return void 0;
|
|
@@ -19340,7 +20028,7 @@ function createJiraTools(store) {
|
|
|
19340
20028
|
const jiraUserConfig = loadUserConfig().jira;
|
|
19341
20029
|
return [
|
|
19342
20030
|
// --- Local read tools ---
|
|
19343
|
-
|
|
20031
|
+
tool20(
|
|
19344
20032
|
"list_jira_issues",
|
|
19345
20033
|
"List locally synced Jira issues (JI-xxx documents), optionally filtered by status or Jira key",
|
|
19346
20034
|
{
|
|
@@ -19368,7 +20056,7 @@ function createJiraTools(store) {
|
|
|
19368
20056
|
},
|
|
19369
20057
|
{ annotations: { readOnlyHint: true } }
|
|
19370
20058
|
),
|
|
19371
|
-
|
|
20059
|
+
tool20(
|
|
19372
20060
|
"get_jira_issue",
|
|
19373
20061
|
"Get the full content of a locally synced Jira issue by local ID (JI-xxx) or Jira key (PROJ-123)",
|
|
19374
20062
|
{
|
|
@@ -19401,7 +20089,7 @@ function createJiraTools(store) {
|
|
|
19401
20089
|
{ annotations: { readOnlyHint: true } }
|
|
19402
20090
|
),
|
|
19403
20091
|
// --- Jira → Local tools ---
|
|
19404
|
-
|
|
20092
|
+
tool20(
|
|
19405
20093
|
"pull_jira_issue",
|
|
19406
20094
|
"Fetch a single Jira issue by key and create/update a local JI-xxx document",
|
|
19407
20095
|
{
|
|
@@ -19448,7 +20136,7 @@ function createJiraTools(store) {
|
|
|
19448
20136
|
};
|
|
19449
20137
|
}
|
|
19450
20138
|
),
|
|
19451
|
-
|
|
20139
|
+
tool20(
|
|
19452
20140
|
"pull_jira_issues_jql",
|
|
19453
20141
|
"Bulk fetch Jira issues via JQL query and create/update local JI-xxx documents",
|
|
19454
20142
|
{
|
|
@@ -19496,7 +20184,7 @@ function createJiraTools(store) {
|
|
|
19496
20184
|
}
|
|
19497
20185
|
),
|
|
19498
20186
|
// --- Local → Jira tools ---
|
|
19499
|
-
|
|
20187
|
+
tool20(
|
|
19500
20188
|
"push_artifact_to_jira",
|
|
19501
20189
|
"Create a Jira issue from any Marvin artifact (D/A/Q/F/E) and create a tracking JI-xxx document",
|
|
19502
20190
|
{
|
|
@@ -19557,7 +20245,7 @@ function createJiraTools(store) {
|
|
|
19557
20245
|
}
|
|
19558
20246
|
),
|
|
19559
20247
|
// --- Bidirectional sync ---
|
|
19560
|
-
|
|
20248
|
+
tool20(
|
|
19561
20249
|
"sync_jira_issue",
|
|
19562
20250
|
"Bidirectional sync: push local title/description to Jira, pull latest status/assignee/labels back",
|
|
19563
20251
|
{
|
|
@@ -19598,7 +20286,7 @@ function createJiraTools(store) {
|
|
|
19598
20286
|
}
|
|
19599
20287
|
),
|
|
19600
20288
|
// --- Local link tool ---
|
|
19601
|
-
|
|
20289
|
+
tool20(
|
|
19602
20290
|
"link_artifact_to_jira",
|
|
19603
20291
|
"Add a Marvin artifact ID to a JI-xxx document's linkedArtifacts field",
|
|
19604
20292
|
{
|
|
@@ -19686,13 +20374,13 @@ var jiraSkill = {
|
|
|
19686
20374
|
**Available tools:**
|
|
19687
20375
|
- \`list_jira_issues\` / \`get_jira_issue\` \u2014 browse locally synced Jira issues
|
|
19688
20376
|
- \`pull_jira_issue\` / \`pull_jira_issues_jql\` \u2014 import issues from Jira by key or JQL query
|
|
19689
|
-
- \`push_artifact_to_jira\` \u2014 create a Jira issue from a Marvin artifact (decision, action, epic, etc.)
|
|
20377
|
+
- \`push_artifact_to_jira\` \u2014 create a Jira issue from a Marvin artifact (decision, action, epic, task, etc.)
|
|
19690
20378
|
- \`sync_jira_issue\` \u2014 bidirectional sync of a local JI-xxx with Jira
|
|
19691
20379
|
- \`link_artifact_to_jira\` \u2014 link a Marvin artifact to an existing JI-xxx
|
|
19692
20380
|
|
|
19693
20381
|
**As Tech Lead, use Jira integration to:**
|
|
19694
20382
|
- Pull technical issues and bugs for sprint planning and estimation
|
|
19695
|
-
- Push epics and technical decisions to Jira for cross-team visibility
|
|
20383
|
+
- Push epics, tasks, and technical decisions to Jira for cross-team visibility
|
|
19696
20384
|
- Bidirectional sync to keep local governance and Jira in alignment
|
|
19697
20385
|
- Use JQL queries to track technical debt (e.g. \`labels = "tech-debt" AND status != "Done"\`)`,
|
|
19698
20386
|
"delivery-manager": `You have the **Jira Integration** skill. You can pull issues from Jira and push Marvin artifacts to Jira.
|
|
@@ -19706,16 +20394,420 @@ var jiraSkill = {
|
|
|
19706
20394
|
|
|
19707
20395
|
**As Delivery Manager, use Jira integration to:**
|
|
19708
20396
|
- Pull sprint issues for tracking progress and blockers
|
|
19709
|
-
- Push actions and
|
|
20397
|
+
- Push actions, decisions, and tasks to Jira for stakeholder visibility
|
|
19710
20398
|
- Use JQL queries for reporting (e.g. \`sprint in openSprints() AND assignee = currentUser()\`)
|
|
19711
20399
|
- Sync status between Marvin governance items and Jira issues`
|
|
19712
20400
|
}
|
|
19713
20401
|
};
|
|
19714
20402
|
|
|
20403
|
+
// src/skills/builtin/prd-generator/tools.ts
|
|
20404
|
+
import * as fs6 from "fs";
|
|
20405
|
+
import * as path6 from "path";
|
|
20406
|
+
import { tool as tool21 } from "@anthropic-ai/claude-agent-sdk";
|
|
20407
|
+
var PRIORITY_ORDER2 = {
|
|
20408
|
+
critical: 0,
|
|
20409
|
+
high: 1,
|
|
20410
|
+
medium: 2,
|
|
20411
|
+
low: 3
|
|
20412
|
+
};
|
|
20413
|
+
function priorityRank2(p) {
|
|
20414
|
+
return PRIORITY_ORDER2[p ?? ""] ?? 99;
|
|
20415
|
+
}
|
|
20416
|
+
function gatherContext(store, focusFeature, includeDecisions = true, includeQuestions = true) {
|
|
20417
|
+
const allFeatures = store.list({ type: "feature" });
|
|
20418
|
+
const allEpics = store.list({ type: "epic" });
|
|
20419
|
+
const allTasks = store.list({ type: "task" });
|
|
20420
|
+
const allDecisions = includeDecisions ? store.list({ type: "decision" }) : [];
|
|
20421
|
+
const allQuestions = includeQuestions ? store.list({ type: "question" }) : [];
|
|
20422
|
+
const allActions = store.list({ type: "action" });
|
|
20423
|
+
let features = allFeatures;
|
|
20424
|
+
let epics = allEpics;
|
|
20425
|
+
let tasks = allTasks;
|
|
20426
|
+
if (focusFeature) {
|
|
20427
|
+
features = features.filter((f) => f.frontmatter.id === focusFeature);
|
|
20428
|
+
const featureIds = new Set(features.map((f) => f.frontmatter.id));
|
|
20429
|
+
epics = epics.filter(
|
|
20430
|
+
(e) => normalizeLinkedFeatures(e.frontmatter.linkedFeature).some((id) => featureIds.has(id))
|
|
20431
|
+
);
|
|
20432
|
+
const epicIds2 = new Set(epics.map((e) => e.frontmatter.id));
|
|
20433
|
+
tasks = tasks.filter(
|
|
20434
|
+
(t) => normalizeLinkedEpics(t.frontmatter.linkedEpic).some((id) => epicIds2.has(id))
|
|
20435
|
+
);
|
|
20436
|
+
}
|
|
20437
|
+
const featuresByStatus = {};
|
|
20438
|
+
for (const f of features) {
|
|
20439
|
+
featuresByStatus[f.frontmatter.status] = (featuresByStatus[f.frontmatter.status] ?? 0) + 1;
|
|
20440
|
+
}
|
|
20441
|
+
const epicsByStatus = {};
|
|
20442
|
+
for (const e of epics) {
|
|
20443
|
+
epicsByStatus[e.frontmatter.status] = (epicsByStatus[e.frontmatter.status] ?? 0) + 1;
|
|
20444
|
+
}
|
|
20445
|
+
const epicIds = new Set(epics.map((e) => e.frontmatter.id));
|
|
20446
|
+
return {
|
|
20447
|
+
features: features.sort((a, b) => priorityRank2(a.frontmatter.priority) - priorityRank2(b.frontmatter.priority)).map((f) => ({
|
|
20448
|
+
id: f.frontmatter.id,
|
|
20449
|
+
title: f.frontmatter.title,
|
|
20450
|
+
status: f.frontmatter.status,
|
|
20451
|
+
priority: f.frontmatter.priority ?? "medium",
|
|
20452
|
+
content: f.content,
|
|
20453
|
+
linkedEpicCount: epics.filter(
|
|
20454
|
+
(e) => normalizeLinkedFeatures(e.frontmatter.linkedFeature).includes(f.frontmatter.id)
|
|
20455
|
+
).length
|
|
20456
|
+
})),
|
|
20457
|
+
epics: epics.map((e) => ({
|
|
20458
|
+
id: e.frontmatter.id,
|
|
20459
|
+
title: e.frontmatter.title,
|
|
20460
|
+
status: e.frontmatter.status,
|
|
20461
|
+
linkedFeature: normalizeLinkedFeatures(e.frontmatter.linkedFeature),
|
|
20462
|
+
targetDate: e.frontmatter.targetDate ?? null,
|
|
20463
|
+
estimatedEffort: e.frontmatter.estimatedEffort ?? null,
|
|
20464
|
+
content: e.content,
|
|
20465
|
+
linkedTaskCount: tasks.filter(
|
|
20466
|
+
(t) => normalizeLinkedEpics(t.frontmatter.linkedEpic).includes(e.frontmatter.id)
|
|
20467
|
+
).length
|
|
20468
|
+
})),
|
|
20469
|
+
tasks: tasks.map((t) => ({
|
|
20470
|
+
id: t.frontmatter.id,
|
|
20471
|
+
title: t.frontmatter.title,
|
|
20472
|
+
status: t.frontmatter.status,
|
|
20473
|
+
linkedEpic: normalizeLinkedEpics(t.frontmatter.linkedEpic),
|
|
20474
|
+
acceptanceCriteria: t.frontmatter.acceptanceCriteria ?? null,
|
|
20475
|
+
technicalNotes: t.frontmatter.technicalNotes ?? null,
|
|
20476
|
+
complexity: t.frontmatter.complexity ?? null,
|
|
20477
|
+
estimatedPoints: t.frontmatter.estimatedPoints ?? null,
|
|
20478
|
+
priority: t.frontmatter.priority ?? null
|
|
20479
|
+
})),
|
|
20480
|
+
decisions: allDecisions.map((d) => ({
|
|
20481
|
+
id: d.frontmatter.id,
|
|
20482
|
+
title: d.frontmatter.title,
|
|
20483
|
+
status: d.frontmatter.status,
|
|
20484
|
+
content: d.content
|
|
20485
|
+
})),
|
|
20486
|
+
questions: allQuestions.map((q) => ({
|
|
20487
|
+
id: q.frontmatter.id,
|
|
20488
|
+
title: q.frontmatter.title,
|
|
20489
|
+
status: q.frontmatter.status,
|
|
20490
|
+
content: q.content
|
|
20491
|
+
})),
|
|
20492
|
+
actions: allActions.filter((a) => {
|
|
20493
|
+
if (!focusFeature) return true;
|
|
20494
|
+
const tags = a.frontmatter.tags ?? [];
|
|
20495
|
+
return tags.some((t) => t.startsWith("epic:") && epicIds.has(t.replace("epic:", "")));
|
|
20496
|
+
}).map((a) => ({
|
|
20497
|
+
id: a.frontmatter.id,
|
|
20498
|
+
title: a.frontmatter.title,
|
|
20499
|
+
status: a.frontmatter.status,
|
|
20500
|
+
owner: a.frontmatter.owner ?? null,
|
|
20501
|
+
priority: a.frontmatter.priority ?? null,
|
|
20502
|
+
dueDate: a.frontmatter.dueDate ?? null
|
|
20503
|
+
})),
|
|
20504
|
+
summary: {
|
|
20505
|
+
totalFeatures: features.length,
|
|
20506
|
+
totalEpics: epics.length,
|
|
20507
|
+
totalTasks: tasks.length,
|
|
20508
|
+
featuresByStatus,
|
|
20509
|
+
epicsByStatus
|
|
20510
|
+
}
|
|
20511
|
+
};
|
|
20512
|
+
}
|
|
20513
|
+
function generateTaskMasterPrd(title, ctx, projectOverview) {
|
|
20514
|
+
const lines = [];
|
|
20515
|
+
lines.push(`# ${title}`);
|
|
20516
|
+
lines.push("");
|
|
20517
|
+
lines.push("## Project Overview");
|
|
20518
|
+
if (projectOverview) {
|
|
20519
|
+
lines.push(projectOverview);
|
|
20520
|
+
} else if (ctx.features.length > 0) {
|
|
20521
|
+
lines.push(`This project encompasses ${ctx.features.length} feature(s) spanning ${ctx.epics.length} epic(s) and ${ctx.tasks.length} implementation task(s).`);
|
|
20522
|
+
}
|
|
20523
|
+
lines.push("");
|
|
20524
|
+
lines.push("## Goals");
|
|
20525
|
+
for (const f of ctx.features) {
|
|
20526
|
+
lines.push(`- **${f.title}** (${f.id}, Priority: ${f.priority}) \u2014 ${f.status}`);
|
|
20527
|
+
}
|
|
20528
|
+
lines.push("");
|
|
20529
|
+
lines.push("## Features and Requirements");
|
|
20530
|
+
lines.push("");
|
|
20531
|
+
for (const feature of ctx.features) {
|
|
20532
|
+
lines.push(`### ${feature.title} (${feature.id}) \u2014 Priority: ${feature.priority}`);
|
|
20533
|
+
lines.push("");
|
|
20534
|
+
if (feature.content) {
|
|
20535
|
+
lines.push(feature.content);
|
|
20536
|
+
lines.push("");
|
|
20537
|
+
}
|
|
20538
|
+
const featureEpics = ctx.epics.filter((e) => e.linkedFeature.includes(feature.id));
|
|
20539
|
+
if (featureEpics.length > 0) {
|
|
20540
|
+
lines.push("#### User Stories / Epics");
|
|
20541
|
+
lines.push("");
|
|
20542
|
+
for (const epic of featureEpics) {
|
|
20543
|
+
const effort = epic.estimatedEffort ? `, Effort: ${epic.estimatedEffort}` : "";
|
|
20544
|
+
lines.push(`- **${epic.id}: ${epic.title}** \u2014 Status: ${epic.status}${effort}`);
|
|
20545
|
+
if (epic.content) {
|
|
20546
|
+
lines.push(` ${epic.content.split("\n")[0]}`);
|
|
20547
|
+
}
|
|
20548
|
+
const epicTasks = ctx.tasks.filter((t) => t.linkedEpic.includes(epic.id));
|
|
20549
|
+
if (epicTasks.length > 0) {
|
|
20550
|
+
lines.push("");
|
|
20551
|
+
lines.push("#### Implementation Tasks");
|
|
20552
|
+
lines.push("");
|
|
20553
|
+
for (const task of epicTasks) {
|
|
20554
|
+
const complexity = task.complexity ? `, Complexity: ${task.complexity}` : "";
|
|
20555
|
+
const points = task.estimatedPoints != null ? `, Points: ${task.estimatedPoints}` : "";
|
|
20556
|
+
lines.push(`- **${task.id}: ${task.title}**${complexity}${points}`);
|
|
20557
|
+
if (task.acceptanceCriteria) {
|
|
20558
|
+
lines.push(` Acceptance Criteria: ${task.acceptanceCriteria}`);
|
|
20559
|
+
}
|
|
20560
|
+
}
|
|
20561
|
+
}
|
|
20562
|
+
}
|
|
20563
|
+
lines.push("");
|
|
20564
|
+
}
|
|
20565
|
+
}
|
|
20566
|
+
const approvedDecisions = ctx.decisions.filter((d) => d.status === "approved" || d.status === "accepted");
|
|
20567
|
+
const openQuestions = ctx.questions.filter((q) => q.status === "open");
|
|
20568
|
+
const technicalNotes = ctx.tasks.filter((t) => t.technicalNotes).map((t) => `- **${t.id}**: ${t.technicalNotes}`);
|
|
20569
|
+
if (approvedDecisions.length > 0 || openQuestions.length > 0 || technicalNotes.length > 0) {
|
|
20570
|
+
lines.push("## Technical Considerations");
|
|
20571
|
+
lines.push("");
|
|
20572
|
+
if (approvedDecisions.length > 0) {
|
|
20573
|
+
lines.push("### Key Decisions");
|
|
20574
|
+
for (const d of approvedDecisions) {
|
|
20575
|
+
lines.push(`- **${d.id}: ${d.title}** \u2014 ${d.content.split("\n")[0]}`);
|
|
20576
|
+
}
|
|
20577
|
+
lines.push("");
|
|
20578
|
+
}
|
|
20579
|
+
if (technicalNotes.length > 0) {
|
|
20580
|
+
lines.push("### Technical Notes");
|
|
20581
|
+
for (const note of technicalNotes) {
|
|
20582
|
+
lines.push(note);
|
|
20583
|
+
}
|
|
20584
|
+
lines.push("");
|
|
20585
|
+
}
|
|
20586
|
+
if (openQuestions.length > 0) {
|
|
20587
|
+
lines.push("### Open Questions");
|
|
20588
|
+
for (const q of openQuestions) {
|
|
20589
|
+
lines.push(`- **${q.id}: ${q.title}** \u2014 ${q.content.split("\n")[0]}`);
|
|
20590
|
+
}
|
|
20591
|
+
lines.push("");
|
|
20592
|
+
}
|
|
20593
|
+
}
|
|
20594
|
+
lines.push("## Implementation Priorities");
|
|
20595
|
+
lines.push("");
|
|
20596
|
+
let priorityIdx = 1;
|
|
20597
|
+
for (const feature of ctx.features) {
|
|
20598
|
+
const featureEpics = ctx.epics.filter((e) => e.linkedFeature.includes(feature.id)).sort((a, b) => {
|
|
20599
|
+
const statusOrder = { "in-progress": 0, planned: 1, done: 2 };
|
|
20600
|
+
return (statusOrder[a.status] ?? 99) - (statusOrder[b.status] ?? 99);
|
|
20601
|
+
});
|
|
20602
|
+
if (featureEpics.length === 0) continue;
|
|
20603
|
+
lines.push(`${priorityIdx}. **${feature.title}** (${feature.priority})`);
|
|
20604
|
+
for (const epic of featureEpics) {
|
|
20605
|
+
const epicTasks = ctx.tasks.filter((t) => t.linkedEpic.includes(epic.id));
|
|
20606
|
+
lines.push(` - ${epic.id}: ${epic.title} (${epic.status}) \u2014 ${epicTasks.length} task(s)`);
|
|
20607
|
+
}
|
|
20608
|
+
priorityIdx++;
|
|
20609
|
+
}
|
|
20610
|
+
lines.push("");
|
|
20611
|
+
return lines.join("\n");
|
|
20612
|
+
}
|
|
20613
|
+
function generateClaudeCodePrd(title, ctx, projectOverview) {
|
|
20614
|
+
const lines = [];
|
|
20615
|
+
lines.push(`# ${title}`);
|
|
20616
|
+
lines.push("");
|
|
20617
|
+
lines.push("## Overview");
|
|
20618
|
+
if (projectOverview) {
|
|
20619
|
+
lines.push(projectOverview);
|
|
20620
|
+
} else if (ctx.features.length > 0) {
|
|
20621
|
+
lines.push(`This project encompasses ${ctx.features.length} feature(s) spanning ${ctx.epics.length} epic(s) and ${ctx.tasks.length} implementation task(s).`);
|
|
20622
|
+
}
|
|
20623
|
+
lines.push("");
|
|
20624
|
+
const approvedDecisions = ctx.decisions.filter((d) => d.status === "approved" || d.status === "accepted");
|
|
20625
|
+
if (approvedDecisions.length > 0) {
|
|
20626
|
+
lines.push("## Architecture & Technical Decisions");
|
|
20627
|
+
lines.push("");
|
|
20628
|
+
for (const d of approvedDecisions) {
|
|
20629
|
+
lines.push(`### ${d.id}: ${d.title}`);
|
|
20630
|
+
lines.push(d.content);
|
|
20631
|
+
lines.push("");
|
|
20632
|
+
}
|
|
20633
|
+
}
|
|
20634
|
+
lines.push("## Implementation Plan");
|
|
20635
|
+
lines.push("");
|
|
20636
|
+
const priorityGroups = {};
|
|
20637
|
+
for (const f of ctx.features) {
|
|
20638
|
+
const group = f.priority === "critical" || f.priority === "high" ? "Phase 1: High Priority" : "Phase 2: Medium & Low Priority";
|
|
20639
|
+
if (!priorityGroups[group]) priorityGroups[group] = [];
|
|
20640
|
+
priorityGroups[group].push(f);
|
|
20641
|
+
}
|
|
20642
|
+
for (const [phase, features] of Object.entries(priorityGroups)) {
|
|
20643
|
+
lines.push(`### ${phase}`);
|
|
20644
|
+
lines.push("");
|
|
20645
|
+
for (const feature of features) {
|
|
20646
|
+
const featureEpics = ctx.epics.filter((e) => e.linkedFeature.includes(feature.id));
|
|
20647
|
+
for (const epic of featureEpics) {
|
|
20648
|
+
lines.push(`- [ ] ${epic.id}: ${epic.title}`);
|
|
20649
|
+
const epicTasks = ctx.tasks.filter((t) => t.linkedEpic.includes(epic.id));
|
|
20650
|
+
for (const task of epicTasks) {
|
|
20651
|
+
const complexity = task.complexity ? `complexity: ${task.complexity}` : "";
|
|
20652
|
+
const points = task.estimatedPoints != null ? `points: ${task.estimatedPoints}` : "";
|
|
20653
|
+
const meta3 = [complexity, points].filter(Boolean).join(", ");
|
|
20654
|
+
lines.push(` - [ ] ${task.id}: ${task.title}${meta3 ? ` (${meta3})` : ""}`);
|
|
20655
|
+
if (task.acceptanceCriteria) {
|
|
20656
|
+
lines.push(` - Acceptance: ${task.acceptanceCriteria}`);
|
|
20657
|
+
}
|
|
20658
|
+
if (task.technicalNotes) {
|
|
20659
|
+
lines.push(` - Notes: ${task.technicalNotes}`);
|
|
20660
|
+
}
|
|
20661
|
+
}
|
|
20662
|
+
}
|
|
20663
|
+
}
|
|
20664
|
+
lines.push("");
|
|
20665
|
+
}
|
|
20666
|
+
const openQuestions = ctx.questions.filter((q) => q.status === "open");
|
|
20667
|
+
if (openQuestions.length > 0) {
|
|
20668
|
+
lines.push("## Open Questions");
|
|
20669
|
+
lines.push("");
|
|
20670
|
+
for (const q of openQuestions) {
|
|
20671
|
+
lines.push(`- **${q.id}: ${q.title}** \u2014 ${q.content.split("\n")[0]}`);
|
|
20672
|
+
}
|
|
20673
|
+
lines.push("");
|
|
20674
|
+
}
|
|
20675
|
+
return lines.join("\n");
|
|
20676
|
+
}
|
|
20677
|
+
function createPrdTools(store) {
|
|
20678
|
+
return [
|
|
20679
|
+
tool21(
|
|
20680
|
+
"gather_prd_context",
|
|
20681
|
+
"Aggregate all governance artifacts (features, epics, tasks, decisions, questions, actions) into structured JSON for PRD generation",
|
|
20682
|
+
{
|
|
20683
|
+
focusFeature: external_exports.string().optional().describe("Filter context to a specific feature ID (e.g. 'F-001')"),
|
|
20684
|
+
includeDecisions: external_exports.boolean().optional().describe("Include decisions in context (default: true)"),
|
|
20685
|
+
includeQuestions: external_exports.boolean().optional().describe("Include questions in context (default: true)")
|
|
20686
|
+
},
|
|
20687
|
+
async (args) => {
|
|
20688
|
+
const ctx = gatherContext(store, args.focusFeature, args.includeDecisions ?? true, args.includeQuestions ?? true);
|
|
20689
|
+
return {
|
|
20690
|
+
content: [{ type: "text", text: JSON.stringify(ctx, null, 2) }]
|
|
20691
|
+
};
|
|
20692
|
+
},
|
|
20693
|
+
{ annotations: { readOnlyHint: true } }
|
|
20694
|
+
),
|
|
20695
|
+
tool21(
|
|
20696
|
+
"generate_prd",
|
|
20697
|
+
"Generate a PRD document from governance artifacts and save it as a PRD-xxx document",
|
|
20698
|
+
{
|
|
20699
|
+
title: external_exports.string().describe("PRD title"),
|
|
20700
|
+
format: external_exports.enum(["taskmaster", "claude-code"]).describe("Output format: 'taskmaster' for Claude TaskMaster parse_prd, 'claude-code' for Claude Code consumption"),
|
|
20701
|
+
projectOverview: external_exports.string().optional().describe("Project overview text (synthesized from features if not provided)"),
|
|
20702
|
+
focusFeature: external_exports.string().optional().describe("Focus on a specific feature ID (e.g. 'F-001')"),
|
|
20703
|
+
tags: external_exports.array(external_exports.string()).optional().describe("Tags for the PRD document")
|
|
20704
|
+
},
|
|
20705
|
+
async (args) => {
|
|
20706
|
+
const ctx = gatherContext(store, args.focusFeature);
|
|
20707
|
+
const prdContent = args.format === "taskmaster" ? generateTaskMasterPrd(args.title, ctx, args.projectOverview) : generateClaudeCodePrd(args.title, ctx, args.projectOverview);
|
|
20708
|
+
const frontmatter = {
|
|
20709
|
+
title: args.title,
|
|
20710
|
+
status: "draft",
|
|
20711
|
+
format: args.format
|
|
20712
|
+
};
|
|
20713
|
+
if (args.focusFeature) frontmatter.focusFeature = args.focusFeature;
|
|
20714
|
+
if (args.tags) frontmatter.tags = args.tags;
|
|
20715
|
+
const doc = store.create("prd", frontmatter, prdContent);
|
|
20716
|
+
return {
|
|
20717
|
+
content: [
|
|
20718
|
+
{
|
|
20719
|
+
type: "text",
|
|
20720
|
+
text: `Generated PRD ${doc.frontmatter.id}: "${args.title}" (format: ${args.format}, ${ctx.summary.totalFeatures} features, ${ctx.summary.totalEpics} epics, ${ctx.summary.totalTasks} tasks)`
|
|
20721
|
+
}
|
|
20722
|
+
]
|
|
20723
|
+
};
|
|
20724
|
+
}
|
|
20725
|
+
),
|
|
20726
|
+
tool21(
|
|
20727
|
+
"export_prd",
|
|
20728
|
+
"Export a PRD document to a file path for external consumption (e.g. by Claude TaskMaster or Claude Code)",
|
|
20729
|
+
{
|
|
20730
|
+
prdId: external_exports.string().describe("PRD document ID (e.g. 'PRD-001')"),
|
|
20731
|
+
outputPath: external_exports.string().describe("File path to write the PRD content to")
|
|
20732
|
+
},
|
|
20733
|
+
async (args) => {
|
|
20734
|
+
const doc = store.get(args.prdId);
|
|
20735
|
+
if (!doc) {
|
|
20736
|
+
return {
|
|
20737
|
+
content: [{ type: "text", text: `PRD ${args.prdId} not found` }],
|
|
20738
|
+
isError: true
|
|
20739
|
+
};
|
|
20740
|
+
}
|
|
20741
|
+
const outputDir = path6.dirname(args.outputPath);
|
|
20742
|
+
fs6.mkdirSync(outputDir, { recursive: true });
|
|
20743
|
+
fs6.writeFileSync(args.outputPath, doc.content, "utf-8");
|
|
20744
|
+
return {
|
|
20745
|
+
content: [
|
|
20746
|
+
{
|
|
20747
|
+
type: "text",
|
|
20748
|
+
text: `Exported PRD ${args.prdId} to ${args.outputPath}`
|
|
20749
|
+
}
|
|
20750
|
+
]
|
|
20751
|
+
};
|
|
20752
|
+
}
|
|
20753
|
+
)
|
|
20754
|
+
];
|
|
20755
|
+
}
|
|
20756
|
+
|
|
20757
|
+
// src/skills/builtin/prd-generator/index.ts
|
|
20758
|
+
var prdGeneratorSkill = {
|
|
20759
|
+
id: "prd-generator",
|
|
20760
|
+
name: "PRD Generator",
|
|
20761
|
+
description: "Generate PRDs from governance artifacts for TaskMaster or Claude Code",
|
|
20762
|
+
version: "1.0.0",
|
|
20763
|
+
format: "builtin-ts",
|
|
20764
|
+
documentTypeRegistrations: [
|
|
20765
|
+
{ type: "prd", dirName: "prds", idPrefix: "PRD" }
|
|
20766
|
+
],
|
|
20767
|
+
tools: (store) => createPrdTools(store),
|
|
20768
|
+
promptFragments: {
|
|
20769
|
+
"tech-lead": `You have the **PRD Generator** skill. You can generate Product Requirements Documents from governance artifacts.
|
|
20770
|
+
|
|
20771
|
+
**Available tools:**
|
|
20772
|
+
- \`gather_prd_context\` \u2014 aggregate features, epics, tasks, decisions, questions, and actions into structured JSON for analysis
|
|
20773
|
+
- \`generate_prd\` \u2014 generate a formatted PRD document and save it as PRD-xxx. Supports "taskmaster" format (for Claude TaskMaster parse_prd) and "claude-code" format (for Claude Code consumption)
|
|
20774
|
+
- \`export_prd\` \u2014 export a PRD document to a file path for external use
|
|
20775
|
+
|
|
20776
|
+
**As Tech Lead, use PRD generation to:**
|
|
20777
|
+
- Create comprehensive PRDs that capture the full governance context
|
|
20778
|
+
- Export TaskMaster-format PRDs for automated task breakdown via \`parse_prd\`
|
|
20779
|
+
- Export Claude Code-format PRDs as implementation plans with checklists
|
|
20780
|
+
- Focus PRDs on specific features using the focusFeature parameter`,
|
|
20781
|
+
"delivery-manager": `You have the **PRD Generator** skill. You can generate Product Requirements Documents from governance artifacts.
|
|
20782
|
+
|
|
20783
|
+
**Available tools:**
|
|
20784
|
+
- \`gather_prd_context\` \u2014 aggregate all governance artifacts into structured JSON for review
|
|
20785
|
+
- \`generate_prd\` \u2014 generate a formatted PRD document (taskmaster or claude-code format)
|
|
20786
|
+
- \`export_prd\` \u2014 export a PRD to a file path
|
|
20787
|
+
|
|
20788
|
+
**As Delivery Manager, use PRD generation to:**
|
|
20789
|
+
- Generate PRDs for stakeholder communication and project documentation
|
|
20790
|
+
- Review aggregated project context before sprint planning
|
|
20791
|
+
- Export PRDs to share with external teams or tools`,
|
|
20792
|
+
"product-owner": `You have the **PRD Generator** skill. You can generate Product Requirements Documents from governance artifacts.
|
|
20793
|
+
|
|
20794
|
+
**Available tools:**
|
|
20795
|
+
- \`gather_prd_context\` \u2014 aggregate features, epics, tasks, and decisions into structured JSON
|
|
20796
|
+
- \`generate_prd\` \u2014 generate a formatted PRD document
|
|
20797
|
+
- \`export_prd\` \u2014 export a PRD to a file path
|
|
20798
|
+
|
|
20799
|
+
**As Product Owner, use PRD generation to:**
|
|
20800
|
+
- Generate PRDs that capture feature requirements and priorities
|
|
20801
|
+
- Review the complete governance context for product planning
|
|
20802
|
+
- Export PRDs for stakeholder review and sign-off`
|
|
20803
|
+
}
|
|
20804
|
+
};
|
|
20805
|
+
|
|
19715
20806
|
// src/skills/registry.ts
|
|
19716
20807
|
var BUILTIN_SKILLS = {
|
|
19717
20808
|
"governance-review": governanceReviewSkill,
|
|
19718
|
-
"jira": jiraSkill
|
|
20809
|
+
"jira": jiraSkill,
|
|
20810
|
+
"prd-generator": prdGeneratorSkill
|
|
19719
20811
|
};
|
|
19720
20812
|
var GOVERNANCE_TOOL_NAMES = [
|
|
19721
20813
|
"mcp__marvin-governance__list_decisions",
|
|
@@ -19736,13 +20828,13 @@ var GOVERNANCE_TOOL_NAMES = [
|
|
|
19736
20828
|
];
|
|
19737
20829
|
function getBuiltinSkillsDir() {
|
|
19738
20830
|
const thisFile = fileURLToPath(import.meta.url);
|
|
19739
|
-
return
|
|
20831
|
+
return path7.join(path7.dirname(thisFile), "builtin");
|
|
19740
20832
|
}
|
|
19741
20833
|
function loadSkillFromDirectory(dirPath) {
|
|
19742
|
-
const skillMdPath =
|
|
19743
|
-
if (!
|
|
20834
|
+
const skillMdPath = path7.join(dirPath, "SKILL.md");
|
|
20835
|
+
if (!fs7.existsSync(skillMdPath)) return void 0;
|
|
19744
20836
|
try {
|
|
19745
|
-
const raw =
|
|
20837
|
+
const raw = fs7.readFileSync(skillMdPath, "utf-8");
|
|
19746
20838
|
const { data, content } = matter2(raw);
|
|
19747
20839
|
if (!data.name || !data.description) return void 0;
|
|
19748
20840
|
const metadata = data.metadata ?? {};
|
|
@@ -19753,13 +20845,13 @@ function loadSkillFromDirectory(dirPath) {
|
|
|
19753
20845
|
if (wildcardPrompt) {
|
|
19754
20846
|
promptFragments["*"] = wildcardPrompt;
|
|
19755
20847
|
}
|
|
19756
|
-
const personasDir =
|
|
19757
|
-
if (
|
|
20848
|
+
const personasDir = path7.join(dirPath, "personas");
|
|
20849
|
+
if (fs7.existsSync(personasDir)) {
|
|
19758
20850
|
try {
|
|
19759
|
-
for (const file2 of
|
|
20851
|
+
for (const file2 of fs7.readdirSync(personasDir)) {
|
|
19760
20852
|
if (!file2.endsWith(".md")) continue;
|
|
19761
20853
|
const personaId = file2.replace(/\.md$/, "");
|
|
19762
|
-
const personaPrompt =
|
|
20854
|
+
const personaPrompt = fs7.readFileSync(path7.join(personasDir, file2), "utf-8").trim();
|
|
19763
20855
|
if (personaPrompt) {
|
|
19764
20856
|
promptFragments[personaId] = personaPrompt;
|
|
19765
20857
|
}
|
|
@@ -19768,10 +20860,10 @@ function loadSkillFromDirectory(dirPath) {
|
|
|
19768
20860
|
}
|
|
19769
20861
|
}
|
|
19770
20862
|
let actions;
|
|
19771
|
-
const actionsPath =
|
|
19772
|
-
if (
|
|
20863
|
+
const actionsPath = path7.join(dirPath, "actions.yaml");
|
|
20864
|
+
if (fs7.existsSync(actionsPath)) {
|
|
19773
20865
|
try {
|
|
19774
|
-
const actionsRaw =
|
|
20866
|
+
const actionsRaw = fs7.readFileSync(actionsPath, "utf-8");
|
|
19775
20867
|
actions = YAML3.parse(actionsRaw);
|
|
19776
20868
|
} catch {
|
|
19777
20869
|
}
|
|
@@ -19798,10 +20890,10 @@ function loadAllSkills(marvinDir) {
|
|
|
19798
20890
|
}
|
|
19799
20891
|
try {
|
|
19800
20892
|
const builtinDir = getBuiltinSkillsDir();
|
|
19801
|
-
if (
|
|
19802
|
-
for (const entry of
|
|
19803
|
-
const entryPath =
|
|
19804
|
-
if (!
|
|
20893
|
+
if (fs7.existsSync(builtinDir)) {
|
|
20894
|
+
for (const entry of fs7.readdirSync(builtinDir)) {
|
|
20895
|
+
const entryPath = path7.join(builtinDir, entry);
|
|
20896
|
+
if (!fs7.statSync(entryPath).isDirectory()) continue;
|
|
19805
20897
|
if (skills.has(entry)) continue;
|
|
19806
20898
|
const skill = loadSkillFromDirectory(entryPath);
|
|
19807
20899
|
if (skill) skills.set(skill.id, skill);
|
|
@@ -19810,18 +20902,18 @@ function loadAllSkills(marvinDir) {
|
|
|
19810
20902
|
} catch {
|
|
19811
20903
|
}
|
|
19812
20904
|
if (marvinDir) {
|
|
19813
|
-
const skillsDir =
|
|
19814
|
-
if (
|
|
20905
|
+
const skillsDir = path7.join(marvinDir, "skills");
|
|
20906
|
+
if (fs7.existsSync(skillsDir)) {
|
|
19815
20907
|
let entries;
|
|
19816
20908
|
try {
|
|
19817
|
-
entries =
|
|
20909
|
+
entries = fs7.readdirSync(skillsDir);
|
|
19818
20910
|
} catch {
|
|
19819
20911
|
entries = [];
|
|
19820
20912
|
}
|
|
19821
20913
|
for (const entry of entries) {
|
|
19822
|
-
const entryPath =
|
|
20914
|
+
const entryPath = path7.join(skillsDir, entry);
|
|
19823
20915
|
try {
|
|
19824
|
-
if (
|
|
20916
|
+
if (fs7.statSync(entryPath).isDirectory()) {
|
|
19825
20917
|
const skill = loadSkillFromDirectory(entryPath);
|
|
19826
20918
|
if (skill) skills.set(skill.id, skill);
|
|
19827
20919
|
continue;
|
|
@@ -19831,7 +20923,7 @@ function loadAllSkills(marvinDir) {
|
|
|
19831
20923
|
}
|
|
19832
20924
|
if (!entry.endsWith(".yaml") && !entry.endsWith(".yml")) continue;
|
|
19833
20925
|
try {
|
|
19834
|
-
const raw =
|
|
20926
|
+
const raw = fs7.readFileSync(entryPath, "utf-8");
|
|
19835
20927
|
const parsed = YAML3.parse(raw);
|
|
19836
20928
|
if (!parsed?.id || !parsed?.name || !parsed?.version) continue;
|
|
19837
20929
|
const skill = {
|
|
@@ -19936,12 +21028,12 @@ function getSkillAgentDefinitions(skillIds, allSkills) {
|
|
|
19936
21028
|
return agents;
|
|
19937
21029
|
}
|
|
19938
21030
|
function migrateYamlToSkillMd(yamlPath, outputDir) {
|
|
19939
|
-
const raw =
|
|
21031
|
+
const raw = fs7.readFileSync(yamlPath, "utf-8");
|
|
19940
21032
|
const parsed = YAML3.parse(raw);
|
|
19941
21033
|
if (!parsed?.id || !parsed?.name) {
|
|
19942
21034
|
throw new Error(`Invalid skill YAML: missing required fields (id, name)`);
|
|
19943
21035
|
}
|
|
19944
|
-
|
|
21036
|
+
fs7.mkdirSync(outputDir, { recursive: true });
|
|
19945
21037
|
const frontmatter = {
|
|
19946
21038
|
name: parsed.id,
|
|
19947
21039
|
description: parsed.description ?? ""
|
|
@@ -19955,15 +21047,15 @@ function migrateYamlToSkillMd(yamlPath, outputDir) {
|
|
|
19955
21047
|
const skillMd = matter2.stringify(wildcardPrompt ? `
|
|
19956
21048
|
${wildcardPrompt}
|
|
19957
21049
|
` : "\n", frontmatter);
|
|
19958
|
-
|
|
21050
|
+
fs7.writeFileSync(path7.join(outputDir, "SKILL.md"), skillMd, "utf-8");
|
|
19959
21051
|
if (promptFragments) {
|
|
19960
21052
|
const personaKeys = Object.keys(promptFragments).filter((k) => k !== "*");
|
|
19961
21053
|
if (personaKeys.length > 0) {
|
|
19962
|
-
const personasDir =
|
|
19963
|
-
|
|
21054
|
+
const personasDir = path7.join(outputDir, "personas");
|
|
21055
|
+
fs7.mkdirSync(personasDir, { recursive: true });
|
|
19964
21056
|
for (const personaId of personaKeys) {
|
|
19965
|
-
|
|
19966
|
-
|
|
21057
|
+
fs7.writeFileSync(
|
|
21058
|
+
path7.join(personasDir, `${personaId}.md`),
|
|
19967
21059
|
`${promptFragments[personaId]}
|
|
19968
21060
|
`,
|
|
19969
21061
|
"utf-8"
|
|
@@ -19973,8 +21065,8 @@ ${wildcardPrompt}
|
|
|
19973
21065
|
}
|
|
19974
21066
|
const actions = parsed.actions;
|
|
19975
21067
|
if (actions && actions.length > 0) {
|
|
19976
|
-
|
|
19977
|
-
|
|
21068
|
+
fs7.writeFileSync(
|
|
21069
|
+
path7.join(outputDir, "actions.yaml"),
|
|
19978
21070
|
YAML3.stringify(actions),
|
|
19979
21071
|
"utf-8"
|
|
19980
21072
|
);
|
|
@@ -20053,7 +21145,7 @@ function openBrowser(url2) {
|
|
|
20053
21145
|
var runningServer = null;
|
|
20054
21146
|
function createWebTools(store, projectName, navGroups) {
|
|
20055
21147
|
return [
|
|
20056
|
-
|
|
21148
|
+
tool22(
|
|
20057
21149
|
"start_web_dashboard",
|
|
20058
21150
|
"Start the Marvin web dashboard on a local port. Returns the base URL. If already running, returns the existing URL.",
|
|
20059
21151
|
{
|
|
@@ -20085,7 +21177,7 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
20085
21177
|
};
|
|
20086
21178
|
}
|
|
20087
21179
|
),
|
|
20088
|
-
|
|
21180
|
+
tool22(
|
|
20089
21181
|
"stop_web_dashboard",
|
|
20090
21182
|
"Stop the running Marvin web dashboard.",
|
|
20091
21183
|
{},
|
|
@@ -20105,7 +21197,7 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
20105
21197
|
};
|
|
20106
21198
|
}
|
|
20107
21199
|
),
|
|
20108
|
-
|
|
21200
|
+
tool22(
|
|
20109
21201
|
"get_web_dashboard_urls",
|
|
20110
21202
|
"Get all available dashboard page URLs. The dashboard must be running.",
|
|
20111
21203
|
{},
|
|
@@ -20131,7 +21223,7 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
20131
21223
|
},
|
|
20132
21224
|
{ annotations: { readOnlyHint: true } }
|
|
20133
21225
|
),
|
|
20134
|
-
|
|
21226
|
+
tool22(
|
|
20135
21227
|
"get_dashboard_overview",
|
|
20136
21228
|
"Get the project overview data: document type counts and recent activity. Works without the web server running.",
|
|
20137
21229
|
{},
|
|
@@ -20153,7 +21245,7 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
20153
21245
|
},
|
|
20154
21246
|
{ annotations: { readOnlyHint: true } }
|
|
20155
21247
|
),
|
|
20156
|
-
|
|
21248
|
+
tool22(
|
|
20157
21249
|
"get_dashboard_gar",
|
|
20158
21250
|
"Get the GAR (Governance, Actions, Risks) report as JSON. Works without the web server running.",
|
|
20159
21251
|
{},
|
|
@@ -20165,7 +21257,7 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
20165
21257
|
},
|
|
20166
21258
|
{ annotations: { readOnlyHint: true } }
|
|
20167
21259
|
),
|
|
20168
|
-
|
|
21260
|
+
tool22(
|
|
20169
21261
|
"get_dashboard_board",
|
|
20170
21262
|
"Get board data showing documents grouped by status. Optionally filter by document type. Works without the web server running.",
|
|
20171
21263
|
{
|
|
@@ -20217,8 +21309,8 @@ function createMarvinMcpServer(store, options) {
|
|
|
20217
21309
|
}
|
|
20218
21310
|
|
|
20219
21311
|
// src/agent/session.ts
|
|
20220
|
-
import * as
|
|
20221
|
-
import * as
|
|
21312
|
+
import * as fs10 from "fs";
|
|
21313
|
+
import * as path10 from "path";
|
|
20222
21314
|
import * as readline from "readline";
|
|
20223
21315
|
import chalk from "chalk";
|
|
20224
21316
|
import ora from "ora";
|
|
@@ -20227,13 +21319,13 @@ import {
|
|
|
20227
21319
|
} from "@anthropic-ai/claude-agent-sdk";
|
|
20228
21320
|
|
|
20229
21321
|
// src/storage/session-store.ts
|
|
20230
|
-
import * as
|
|
20231
|
-
import * as
|
|
21322
|
+
import * as fs8 from "fs";
|
|
21323
|
+
import * as path8 from "path";
|
|
20232
21324
|
import * as YAML4 from "yaml";
|
|
20233
21325
|
var SessionStore = class {
|
|
20234
21326
|
filePath;
|
|
20235
21327
|
constructor(marvinDir) {
|
|
20236
|
-
this.filePath =
|
|
21328
|
+
this.filePath = path8.join(marvinDir, "sessions.yaml");
|
|
20237
21329
|
}
|
|
20238
21330
|
list() {
|
|
20239
21331
|
const entries = this.load();
|
|
@@ -20274,9 +21366,9 @@ var SessionStore = class {
|
|
|
20274
21366
|
this.write(entries);
|
|
20275
21367
|
}
|
|
20276
21368
|
load() {
|
|
20277
|
-
if (!
|
|
21369
|
+
if (!fs8.existsSync(this.filePath)) return [];
|
|
20278
21370
|
try {
|
|
20279
|
-
const raw =
|
|
21371
|
+
const raw = fs8.readFileSync(this.filePath, "utf-8");
|
|
20280
21372
|
const parsed = YAML4.parse(raw);
|
|
20281
21373
|
if (!Array.isArray(parsed)) return [];
|
|
20282
21374
|
return parsed;
|
|
@@ -20285,11 +21377,11 @@ var SessionStore = class {
|
|
|
20285
21377
|
}
|
|
20286
21378
|
}
|
|
20287
21379
|
write(entries) {
|
|
20288
|
-
const dir =
|
|
20289
|
-
if (!
|
|
20290
|
-
|
|
21380
|
+
const dir = path8.dirname(this.filePath);
|
|
21381
|
+
if (!fs8.existsSync(dir)) {
|
|
21382
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
20291
21383
|
}
|
|
20292
|
-
|
|
21384
|
+
fs8.writeFileSync(this.filePath, YAML4.stringify(entries), "utf-8");
|
|
20293
21385
|
}
|
|
20294
21386
|
};
|
|
20295
21387
|
|
|
@@ -20325,8 +21417,8 @@ function slugify3(text) {
|
|
|
20325
21417
|
}
|
|
20326
21418
|
|
|
20327
21419
|
// src/sources/manifest.ts
|
|
20328
|
-
import * as
|
|
20329
|
-
import * as
|
|
21420
|
+
import * as fs9 from "fs";
|
|
21421
|
+
import * as path9 from "path";
|
|
20330
21422
|
import * as crypto from "crypto";
|
|
20331
21423
|
import * as YAML5 from "yaml";
|
|
20332
21424
|
var MANIFEST_FILE = ".manifest.yaml";
|
|
@@ -20339,37 +21431,37 @@ var SourceManifestManager = class {
|
|
|
20339
21431
|
manifestPath;
|
|
20340
21432
|
sourcesDir;
|
|
20341
21433
|
constructor(marvinDir) {
|
|
20342
|
-
this.sourcesDir =
|
|
20343
|
-
this.manifestPath =
|
|
21434
|
+
this.sourcesDir = path9.join(marvinDir, "sources");
|
|
21435
|
+
this.manifestPath = path9.join(this.sourcesDir, MANIFEST_FILE);
|
|
20344
21436
|
this.manifest = this.load();
|
|
20345
21437
|
}
|
|
20346
21438
|
load() {
|
|
20347
|
-
if (!
|
|
21439
|
+
if (!fs9.existsSync(this.manifestPath)) {
|
|
20348
21440
|
return emptyManifest();
|
|
20349
21441
|
}
|
|
20350
|
-
const raw =
|
|
21442
|
+
const raw = fs9.readFileSync(this.manifestPath, "utf-8");
|
|
20351
21443
|
const parsed = YAML5.parse(raw);
|
|
20352
21444
|
return parsed ?? emptyManifest();
|
|
20353
21445
|
}
|
|
20354
21446
|
save() {
|
|
20355
|
-
|
|
20356
|
-
|
|
21447
|
+
fs9.mkdirSync(this.sourcesDir, { recursive: true });
|
|
21448
|
+
fs9.writeFileSync(this.manifestPath, YAML5.stringify(this.manifest), "utf-8");
|
|
20357
21449
|
}
|
|
20358
21450
|
scan() {
|
|
20359
21451
|
const added = [];
|
|
20360
21452
|
const changed = [];
|
|
20361
21453
|
const removed = [];
|
|
20362
|
-
if (!
|
|
21454
|
+
if (!fs9.existsSync(this.sourcesDir)) {
|
|
20363
21455
|
return { added, changed, removed };
|
|
20364
21456
|
}
|
|
20365
21457
|
const onDisk = new Set(
|
|
20366
|
-
|
|
20367
|
-
const ext =
|
|
21458
|
+
fs9.readdirSync(this.sourcesDir).filter((f) => {
|
|
21459
|
+
const ext = path9.extname(f).toLowerCase();
|
|
20368
21460
|
return SOURCE_EXTENSIONS.includes(ext);
|
|
20369
21461
|
})
|
|
20370
21462
|
);
|
|
20371
21463
|
for (const fileName of onDisk) {
|
|
20372
|
-
const filePath =
|
|
21464
|
+
const filePath = path9.join(this.sourcesDir, fileName);
|
|
20373
21465
|
const hash2 = this.hashFile(filePath);
|
|
20374
21466
|
const existing = this.manifest.files[fileName];
|
|
20375
21467
|
if (!existing) {
|
|
@@ -20432,7 +21524,7 @@ var SourceManifestManager = class {
|
|
|
20432
21524
|
this.save();
|
|
20433
21525
|
}
|
|
20434
21526
|
hashFile(filePath) {
|
|
20435
|
-
const content =
|
|
21527
|
+
const content = fs9.readFileSync(filePath);
|
|
20436
21528
|
return crypto.createHash("sha256").update(content).digest("hex");
|
|
20437
21529
|
}
|
|
20438
21530
|
};
|
|
@@ -20447,8 +21539,8 @@ async function startSession(options) {
|
|
|
20447
21539
|
const skillRegistrations = collectSkillRegistrations(skillIds, allSkills);
|
|
20448
21540
|
const store = new DocumentStore(marvinDir, [...pluginRegistrations, ...skillRegistrations]);
|
|
20449
21541
|
const sessionStore = new SessionStore(marvinDir);
|
|
20450
|
-
const sourcesDir =
|
|
20451
|
-
const hasSourcesDir =
|
|
21542
|
+
const sourcesDir = path10.join(marvinDir, "sources");
|
|
21543
|
+
const hasSourcesDir = fs10.existsSync(sourcesDir);
|
|
20452
21544
|
const manifest = hasSourcesDir ? new SourceManifestManager(marvinDir) : void 0;
|
|
20453
21545
|
const pluginTools = plugin ? getPluginTools(plugin, store, marvinDir) : [];
|
|
20454
21546
|
const pluginPromptFragment = plugin ? getPluginPromptFragment(plugin, persona.id) : void 0;
|
|
@@ -20657,13 +21749,13 @@ Session ended with error: ${message.subtype}`));
|
|
|
20657
21749
|
}
|
|
20658
21750
|
|
|
20659
21751
|
// src/mcp/stdio-server.ts
|
|
20660
|
-
import * as
|
|
20661
|
-
import * as
|
|
21752
|
+
import * as fs11 from "fs";
|
|
21753
|
+
import * as path11 from "path";
|
|
20662
21754
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
20663
21755
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
20664
21756
|
|
|
20665
21757
|
// src/skills/action-tools.ts
|
|
20666
|
-
import { tool as
|
|
21758
|
+
import { tool as tool23 } from "@anthropic-ai/claude-agent-sdk";
|
|
20667
21759
|
|
|
20668
21760
|
// src/skills/action-runner.ts
|
|
20669
21761
|
import { query as query3 } from "@anthropic-ai/claude-agent-sdk";
|
|
@@ -20729,7 +21821,7 @@ function createSkillActionTools(skills, context) {
|
|
|
20729
21821
|
if (!skill.actions) continue;
|
|
20730
21822
|
for (const action of skill.actions) {
|
|
20731
21823
|
tools.push(
|
|
20732
|
-
|
|
21824
|
+
tool23(
|
|
20733
21825
|
`${skill.id}__${action.id}`,
|
|
20734
21826
|
action.description,
|
|
20735
21827
|
{
|
|
@@ -20821,10 +21913,10 @@ ${lines.join("\n\n")}`;
|
|
|
20821
21913
|
}
|
|
20822
21914
|
|
|
20823
21915
|
// src/mcp/persona-tools.ts
|
|
20824
|
-
import { tool as
|
|
21916
|
+
import { tool as tool24 } from "@anthropic-ai/claude-agent-sdk";
|
|
20825
21917
|
function createPersonaTools(ctx, marvinDir) {
|
|
20826
21918
|
return [
|
|
20827
|
-
|
|
21919
|
+
tool24(
|
|
20828
21920
|
"set_persona",
|
|
20829
21921
|
"Set the active persona for this session. Returns full guidance for the selected persona including behavioral rules, allowed document types, and scope. Call this before working to ensure persona-appropriate behavior.",
|
|
20830
21922
|
{
|
|
@@ -20854,7 +21946,7 @@ ${summaries}`
|
|
|
20854
21946
|
};
|
|
20855
21947
|
}
|
|
20856
21948
|
),
|
|
20857
|
-
|
|
21949
|
+
tool24(
|
|
20858
21950
|
"get_persona_guidance",
|
|
20859
21951
|
"Get guidance for a persona without changing the active persona. If no persona is specified, lists all available personas with summaries.",
|
|
20860
21952
|
{
|
|
@@ -20956,8 +22048,8 @@ function collectTools(marvinDir) {
|
|
|
20956
22048
|
const plugin = resolvePlugin(config2.methodology);
|
|
20957
22049
|
const registrations = plugin?.documentTypeRegistrations ?? [];
|
|
20958
22050
|
const store = new DocumentStore(marvinDir, registrations);
|
|
20959
|
-
const sourcesDir =
|
|
20960
|
-
const hasSourcesDir =
|
|
22051
|
+
const sourcesDir = path11.join(marvinDir, "sources");
|
|
22052
|
+
const hasSourcesDir = fs11.existsSync(sourcesDir);
|
|
20961
22053
|
const manifest = hasSourcesDir ? new SourceManifestManager(marvinDir) : void 0;
|
|
20962
22054
|
const pluginTools = plugin ? getPluginTools(plugin, store, marvinDir) : [];
|
|
20963
22055
|
const sessionStore = new SessionStore(marvinDir);
|
|
@@ -20965,7 +22057,7 @@ function collectTools(marvinDir) {
|
|
|
20965
22057
|
const allSkillIds = [...allSkills.keys()];
|
|
20966
22058
|
const codeSkillTools = getSkillTools(allSkillIds, allSkills, store);
|
|
20967
22059
|
const skillsWithActions = allSkillIds.map((id) => allSkills.get(id)).filter((s) => s.actions && s.actions.length > 0);
|
|
20968
|
-
const projectRoot =
|
|
22060
|
+
const projectRoot = path11.dirname(marvinDir);
|
|
20969
22061
|
const actionTools = createSkillActionTools(skillsWithActions, { store, marvinDir, projectRoot });
|
|
20970
22062
|
const allSkillRegs = collectSkillRegistrations(allSkillIds, allSkills);
|
|
20971
22063
|
const navGroups = buildNavGroups({
|
|
@@ -21032,8 +22124,8 @@ async function startStdioServer(options) {
|
|
|
21032
22124
|
import { Command } from "commander";
|
|
21033
22125
|
|
|
21034
22126
|
// src/cli/commands/init.ts
|
|
21035
|
-
import * as
|
|
21036
|
-
import * as
|
|
22127
|
+
import * as fs12 from "fs";
|
|
22128
|
+
import * as path12 from "path";
|
|
21037
22129
|
import * as YAML6 from "yaml";
|
|
21038
22130
|
import chalk2 from "chalk";
|
|
21039
22131
|
import { input, confirm, select } from "@inquirer/prompts";
|
|
@@ -21099,7 +22191,7 @@ async function initCommand() {
|
|
|
21099
22191
|
}
|
|
21100
22192
|
const projectName = await input({
|
|
21101
22193
|
message: "Project name:",
|
|
21102
|
-
default:
|
|
22194
|
+
default: path12.basename(cwd)
|
|
21103
22195
|
});
|
|
21104
22196
|
const methodology = await select({
|
|
21105
22197
|
message: "Methodology:",
|
|
@@ -21111,21 +22203,21 @@ async function initCommand() {
|
|
|
21111
22203
|
});
|
|
21112
22204
|
const plugin = resolvePlugin(methodology);
|
|
21113
22205
|
const registrations = plugin?.documentTypeRegistrations ?? [];
|
|
21114
|
-
const marvinDir =
|
|
22206
|
+
const marvinDir = path12.join(cwd, ".marvin");
|
|
21115
22207
|
const dirs = [
|
|
21116
22208
|
marvinDir,
|
|
21117
|
-
|
|
21118
|
-
|
|
21119
|
-
|
|
21120
|
-
|
|
21121
|
-
|
|
21122
|
-
|
|
22209
|
+
path12.join(marvinDir, "templates"),
|
|
22210
|
+
path12.join(marvinDir, "docs", "decisions"),
|
|
22211
|
+
path12.join(marvinDir, "docs", "actions"),
|
|
22212
|
+
path12.join(marvinDir, "docs", "questions"),
|
|
22213
|
+
path12.join(marvinDir, "sources"),
|
|
22214
|
+
path12.join(marvinDir, "skills")
|
|
21123
22215
|
];
|
|
21124
22216
|
for (const reg of registrations) {
|
|
21125
|
-
dirs.push(
|
|
22217
|
+
dirs.push(path12.join(marvinDir, "docs", reg.dirName));
|
|
21126
22218
|
}
|
|
21127
22219
|
for (const dir of dirs) {
|
|
21128
|
-
|
|
22220
|
+
fs12.mkdirSync(dir, { recursive: true });
|
|
21129
22221
|
}
|
|
21130
22222
|
const config2 = {
|
|
21131
22223
|
name: projectName,
|
|
@@ -21139,13 +22231,13 @@ async function initCommand() {
|
|
|
21139
22231
|
if (methodology === "sap-aem") {
|
|
21140
22232
|
config2.aem = { currentPhase: "assess-use-case" };
|
|
21141
22233
|
}
|
|
21142
|
-
|
|
21143
|
-
|
|
22234
|
+
fs12.writeFileSync(
|
|
22235
|
+
path12.join(marvinDir, "config.yaml"),
|
|
21144
22236
|
YAML6.stringify(config2),
|
|
21145
22237
|
"utf-8"
|
|
21146
22238
|
);
|
|
21147
|
-
|
|
21148
|
-
|
|
22239
|
+
fs12.writeFileSync(
|
|
22240
|
+
path12.join(marvinDir, "CLAUDE.md"),
|
|
21149
22241
|
getDefaultClaudeMdContent(projectName),
|
|
21150
22242
|
"utf-8"
|
|
21151
22243
|
);
|
|
@@ -21172,18 +22264,18 @@ Initialized Marvin project "${projectName}" in ${cwd}`));
|
|
|
21172
22264
|
const sourceDir = await input({
|
|
21173
22265
|
message: "Path to directory containing source documents:"
|
|
21174
22266
|
});
|
|
21175
|
-
const resolvedDir =
|
|
21176
|
-
if (
|
|
22267
|
+
const resolvedDir = path12.resolve(sourceDir);
|
|
22268
|
+
if (fs12.existsSync(resolvedDir) && fs12.statSync(resolvedDir).isDirectory()) {
|
|
21177
22269
|
const sourceExts = [".pdf", ".md", ".txt"];
|
|
21178
|
-
const files =
|
|
21179
|
-
const ext =
|
|
22270
|
+
const files = fs12.readdirSync(resolvedDir).filter((f) => {
|
|
22271
|
+
const ext = path12.extname(f).toLowerCase();
|
|
21180
22272
|
return sourceExts.includes(ext);
|
|
21181
22273
|
});
|
|
21182
22274
|
let copied = 0;
|
|
21183
22275
|
for (const file2 of files) {
|
|
21184
|
-
const src =
|
|
21185
|
-
const dest =
|
|
21186
|
-
|
|
22276
|
+
const src = path12.join(resolvedDir, file2);
|
|
22277
|
+
const dest = path12.join(marvinDir, "sources", file2);
|
|
22278
|
+
fs12.copyFileSync(src, dest);
|
|
21187
22279
|
copied++;
|
|
21188
22280
|
}
|
|
21189
22281
|
if (copied > 0) {
|
|
@@ -21473,13 +22565,13 @@ async function setApiKey() {
|
|
|
21473
22565
|
}
|
|
21474
22566
|
|
|
21475
22567
|
// src/cli/commands/ingest.ts
|
|
21476
|
-
import * as
|
|
21477
|
-
import * as
|
|
22568
|
+
import * as fs14 from "fs";
|
|
22569
|
+
import * as path14 from "path";
|
|
21478
22570
|
import chalk8 from "chalk";
|
|
21479
22571
|
|
|
21480
22572
|
// src/sources/ingest.ts
|
|
21481
|
-
import * as
|
|
21482
|
-
import * as
|
|
22573
|
+
import * as fs13 from "fs";
|
|
22574
|
+
import * as path13 from "path";
|
|
21483
22575
|
import chalk7 from "chalk";
|
|
21484
22576
|
import ora2 from "ora";
|
|
21485
22577
|
import { query as query4 } from "@anthropic-ai/claude-agent-sdk";
|
|
@@ -21582,15 +22674,15 @@ async function ingestFile(options) {
|
|
|
21582
22674
|
const persona = getPersona(personaId);
|
|
21583
22675
|
const manifest = new SourceManifestManager(marvinDir);
|
|
21584
22676
|
const sourcesDir = manifest.sourcesDir;
|
|
21585
|
-
const filePath =
|
|
21586
|
-
if (!
|
|
22677
|
+
const filePath = path13.join(sourcesDir, fileName);
|
|
22678
|
+
if (!fs13.existsSync(filePath)) {
|
|
21587
22679
|
throw new Error(`Source file not found: ${filePath}`);
|
|
21588
22680
|
}
|
|
21589
|
-
const ext =
|
|
22681
|
+
const ext = path13.extname(fileName).toLowerCase();
|
|
21590
22682
|
const isPdf = ext === ".pdf";
|
|
21591
22683
|
let fileContent = null;
|
|
21592
22684
|
if (!isPdf) {
|
|
21593
|
-
fileContent =
|
|
22685
|
+
fileContent = fs13.readFileSync(filePath, "utf-8");
|
|
21594
22686
|
}
|
|
21595
22687
|
const store = new DocumentStore(marvinDir);
|
|
21596
22688
|
const createdArtifacts = [];
|
|
@@ -21693,9 +22785,9 @@ Ingest ended with error: ${message.subtype}`)
|
|
|
21693
22785
|
async function ingestCommand(file2, options) {
|
|
21694
22786
|
const project = loadProject();
|
|
21695
22787
|
const marvinDir = project.marvinDir;
|
|
21696
|
-
const sourcesDir =
|
|
21697
|
-
if (!
|
|
21698
|
-
|
|
22788
|
+
const sourcesDir = path14.join(marvinDir, "sources");
|
|
22789
|
+
if (!fs14.existsSync(sourcesDir)) {
|
|
22790
|
+
fs14.mkdirSync(sourcesDir, { recursive: true });
|
|
21699
22791
|
}
|
|
21700
22792
|
const manifest = new SourceManifestManager(marvinDir);
|
|
21701
22793
|
manifest.scan();
|
|
@@ -21706,8 +22798,8 @@ async function ingestCommand(file2, options) {
|
|
|
21706
22798
|
return;
|
|
21707
22799
|
}
|
|
21708
22800
|
if (file2) {
|
|
21709
|
-
const filePath =
|
|
21710
|
-
if (!
|
|
22801
|
+
const filePath = path14.join(sourcesDir, file2);
|
|
22802
|
+
if (!fs14.existsSync(filePath)) {
|
|
21711
22803
|
console.log(chalk8.red(`Source file not found: ${file2}`));
|
|
21712
22804
|
console.log(chalk8.dim(`Expected at: ${filePath}`));
|
|
21713
22805
|
console.log(chalk8.dim(`Drop files into .marvin/sources/ and try again.`));
|
|
@@ -21774,7 +22866,7 @@ import ora3 from "ora";
|
|
|
21774
22866
|
import { input as input3 } from "@inquirer/prompts";
|
|
21775
22867
|
|
|
21776
22868
|
// src/git/repository.ts
|
|
21777
|
-
import * as
|
|
22869
|
+
import * as path15 from "path";
|
|
21778
22870
|
import simpleGit from "simple-git";
|
|
21779
22871
|
var MARVIN_GITIGNORE = `node_modules/
|
|
21780
22872
|
.DS_Store
|
|
@@ -21794,7 +22886,7 @@ var DIR_TYPE_LABELS = {
|
|
|
21794
22886
|
function buildCommitMessage(files) {
|
|
21795
22887
|
const counts = /* @__PURE__ */ new Map();
|
|
21796
22888
|
for (const f of files) {
|
|
21797
|
-
const parts2 = f.split(
|
|
22889
|
+
const parts2 = f.split(path15.sep).join("/").split("/");
|
|
21798
22890
|
const docsIdx = parts2.indexOf("docs");
|
|
21799
22891
|
if (docsIdx !== -1 && docsIdx + 1 < parts2.length) {
|
|
21800
22892
|
const dirName = parts2[docsIdx + 1];
|
|
@@ -21834,9 +22926,9 @@ var MarvinGit = class {
|
|
|
21834
22926
|
);
|
|
21835
22927
|
}
|
|
21836
22928
|
await this.git.init();
|
|
21837
|
-
const { writeFileSync:
|
|
21838
|
-
|
|
21839
|
-
|
|
22929
|
+
const { writeFileSync: writeFileSync11 } = await import("fs");
|
|
22930
|
+
writeFileSync11(
|
|
22931
|
+
path15.join(this.marvinDir, ".gitignore"),
|
|
21840
22932
|
MARVIN_GITIGNORE,
|
|
21841
22933
|
"utf-8"
|
|
21842
22934
|
);
|
|
@@ -21956,7 +23048,7 @@ var MarvinGit = class {
|
|
|
21956
23048
|
}
|
|
21957
23049
|
}
|
|
21958
23050
|
static async clone(url2, targetDir) {
|
|
21959
|
-
const marvinDir =
|
|
23051
|
+
const marvinDir = path15.join(targetDir, ".marvin");
|
|
21960
23052
|
const { existsSync: existsSync17 } = await import("fs");
|
|
21961
23053
|
if (existsSync17(marvinDir)) {
|
|
21962
23054
|
throw new GitSyncError(
|
|
@@ -22142,8 +23234,8 @@ async function serveCommand() {
|
|
|
22142
23234
|
}
|
|
22143
23235
|
|
|
22144
23236
|
// src/cli/commands/skills.ts
|
|
22145
|
-
import * as
|
|
22146
|
-
import * as
|
|
23237
|
+
import * as fs15 from "fs";
|
|
23238
|
+
import * as path16 from "path";
|
|
22147
23239
|
import * as YAML7 from "yaml";
|
|
22148
23240
|
import matter3 from "gray-matter";
|
|
22149
23241
|
import chalk10 from "chalk";
|
|
@@ -22249,14 +23341,14 @@ async function skillsRemoveCommand(skillId, options) {
|
|
|
22249
23341
|
}
|
|
22250
23342
|
async function skillsCreateCommand(name) {
|
|
22251
23343
|
const project = loadProject();
|
|
22252
|
-
const skillsDir =
|
|
22253
|
-
|
|
22254
|
-
const skillDir =
|
|
22255
|
-
if (
|
|
23344
|
+
const skillsDir = path16.join(project.marvinDir, "skills");
|
|
23345
|
+
fs15.mkdirSync(skillsDir, { recursive: true });
|
|
23346
|
+
const skillDir = path16.join(skillsDir, name);
|
|
23347
|
+
if (fs15.existsSync(skillDir)) {
|
|
22256
23348
|
console.log(chalk10.yellow(`Skill directory already exists: ${skillDir}`));
|
|
22257
23349
|
return;
|
|
22258
23350
|
}
|
|
22259
|
-
|
|
23351
|
+
fs15.mkdirSync(skillDir, { recursive: true });
|
|
22260
23352
|
const displayName = name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
22261
23353
|
const frontmatter = {
|
|
22262
23354
|
name,
|
|
@@ -22270,7 +23362,7 @@ async function skillsCreateCommand(name) {
|
|
|
22270
23362
|
You have the **${displayName}** skill.
|
|
22271
23363
|
`;
|
|
22272
23364
|
const skillMd = matter3.stringify(body, frontmatter);
|
|
22273
|
-
|
|
23365
|
+
fs15.writeFileSync(path16.join(skillDir, "SKILL.md"), skillMd, "utf-8");
|
|
22274
23366
|
const actions = [
|
|
22275
23367
|
{
|
|
22276
23368
|
id: "run",
|
|
@@ -22280,7 +23372,7 @@ You have the **${displayName}** skill.
|
|
|
22280
23372
|
maxTurns: 5
|
|
22281
23373
|
}
|
|
22282
23374
|
];
|
|
22283
|
-
|
|
23375
|
+
fs15.writeFileSync(path16.join(skillDir, "actions.yaml"), YAML7.stringify(actions), "utf-8");
|
|
22284
23376
|
console.log(chalk10.green(`Created skill: ${skillDir}/`));
|
|
22285
23377
|
console.log(chalk10.dim(" SKILL.md \u2014 skill definition and prompt"));
|
|
22286
23378
|
console.log(chalk10.dim(" actions.yaml \u2014 action definitions"));
|
|
@@ -22288,14 +23380,14 @@ You have the **${displayName}** skill.
|
|
|
22288
23380
|
}
|
|
22289
23381
|
async function skillsMigrateCommand() {
|
|
22290
23382
|
const project = loadProject();
|
|
22291
|
-
const skillsDir =
|
|
22292
|
-
if (!
|
|
23383
|
+
const skillsDir = path16.join(project.marvinDir, "skills");
|
|
23384
|
+
if (!fs15.existsSync(skillsDir)) {
|
|
22293
23385
|
console.log(chalk10.dim("No skills directory found."));
|
|
22294
23386
|
return;
|
|
22295
23387
|
}
|
|
22296
23388
|
let entries;
|
|
22297
23389
|
try {
|
|
22298
|
-
entries =
|
|
23390
|
+
entries = fs15.readdirSync(skillsDir);
|
|
22299
23391
|
} catch {
|
|
22300
23392
|
console.log(chalk10.red("Could not read skills directory."));
|
|
22301
23393
|
return;
|
|
@@ -22307,16 +23399,16 @@ async function skillsMigrateCommand() {
|
|
|
22307
23399
|
}
|
|
22308
23400
|
let migrated = 0;
|
|
22309
23401
|
for (const file2 of yamlFiles) {
|
|
22310
|
-
const yamlPath =
|
|
23402
|
+
const yamlPath = path16.join(skillsDir, file2);
|
|
22311
23403
|
const baseName = file2.replace(/\.(yaml|yml)$/, "");
|
|
22312
|
-
const outputDir =
|
|
22313
|
-
if (
|
|
23404
|
+
const outputDir = path16.join(skillsDir, baseName);
|
|
23405
|
+
if (fs15.existsSync(outputDir)) {
|
|
22314
23406
|
console.log(chalk10.yellow(`Skipping "${file2}" \u2014 directory "${baseName}/" already exists.`));
|
|
22315
23407
|
continue;
|
|
22316
23408
|
}
|
|
22317
23409
|
try {
|
|
22318
23410
|
migrateYamlToSkillMd(yamlPath, outputDir);
|
|
22319
|
-
|
|
23411
|
+
fs15.renameSync(yamlPath, `${yamlPath}.bak`);
|
|
22320
23412
|
console.log(chalk10.green(`Migrated "${file2}" \u2192 "${baseName}/"`));
|
|
22321
23413
|
migrated++;
|
|
22322
23414
|
} catch (err) {
|
|
@@ -22330,35 +23422,35 @@ ${migrated} skill(s) migrated. Original files renamed to *.bak`));
|
|
|
22330
23422
|
}
|
|
22331
23423
|
|
|
22332
23424
|
// src/cli/commands/import.ts
|
|
22333
|
-
import * as
|
|
22334
|
-
import * as
|
|
23425
|
+
import * as fs18 from "fs";
|
|
23426
|
+
import * as path19 from "path";
|
|
22335
23427
|
import chalk11 from "chalk";
|
|
22336
23428
|
|
|
22337
23429
|
// src/import/engine.ts
|
|
22338
|
-
import * as
|
|
22339
|
-
import * as
|
|
23430
|
+
import * as fs17 from "fs";
|
|
23431
|
+
import * as path18 from "path";
|
|
22340
23432
|
import matter5 from "gray-matter";
|
|
22341
23433
|
|
|
22342
23434
|
// src/import/classifier.ts
|
|
22343
|
-
import * as
|
|
22344
|
-
import * as
|
|
23435
|
+
import * as fs16 from "fs";
|
|
23436
|
+
import * as path17 from "path";
|
|
22345
23437
|
import matter4 from "gray-matter";
|
|
22346
23438
|
var RAW_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".pdf", ".txt"]);
|
|
22347
23439
|
var CORE_DIR_NAMES = /* @__PURE__ */ new Set(["decisions", "actions", "questions"]);
|
|
22348
23440
|
var ID_PATTERN = /^[A-Z]+-\d{3,}$/;
|
|
22349
23441
|
function classifyPath(inputPath, knownTypes, knownDirNames) {
|
|
22350
|
-
const resolved =
|
|
22351
|
-
const stat =
|
|
23442
|
+
const resolved = path17.resolve(inputPath);
|
|
23443
|
+
const stat = fs16.statSync(resolved);
|
|
22352
23444
|
if (!stat.isDirectory()) {
|
|
22353
23445
|
return classifyFile(resolved, knownTypes);
|
|
22354
23446
|
}
|
|
22355
|
-
if (
|
|
23447
|
+
if (path17.basename(resolved) === ".marvin" || fs16.existsSync(path17.join(resolved, "config.yaml"))) {
|
|
22356
23448
|
return { type: "marvin-project", inputPath: resolved };
|
|
22357
23449
|
}
|
|
22358
23450
|
const allDirNames = /* @__PURE__ */ new Set([...CORE_DIR_NAMES, ...knownDirNames]);
|
|
22359
|
-
const entries =
|
|
23451
|
+
const entries = fs16.readdirSync(resolved);
|
|
22360
23452
|
const hasDocSubdirs = entries.some(
|
|
22361
|
-
(e) => allDirNames.has(e) &&
|
|
23453
|
+
(e) => allDirNames.has(e) && fs16.statSync(path17.join(resolved, e)).isDirectory()
|
|
22362
23454
|
);
|
|
22363
23455
|
if (hasDocSubdirs) {
|
|
22364
23456
|
return { type: "docs-directory", inputPath: resolved };
|
|
@@ -22367,7 +23459,7 @@ function classifyPath(inputPath, knownTypes, knownDirNames) {
|
|
|
22367
23459
|
if (mdFiles.length > 0) {
|
|
22368
23460
|
const hasMarvinDocs = mdFiles.some((f) => {
|
|
22369
23461
|
try {
|
|
22370
|
-
const raw =
|
|
23462
|
+
const raw = fs16.readFileSync(path17.join(resolved, f), "utf-8");
|
|
22371
23463
|
const { data } = matter4(raw);
|
|
22372
23464
|
return isValidMarvinDocument(data, knownTypes);
|
|
22373
23465
|
} catch {
|
|
@@ -22381,14 +23473,14 @@ function classifyPath(inputPath, knownTypes, knownDirNames) {
|
|
|
22381
23473
|
return { type: "raw-source-dir", inputPath: resolved };
|
|
22382
23474
|
}
|
|
22383
23475
|
function classifyFile(filePath, knownTypes) {
|
|
22384
|
-
const resolved =
|
|
22385
|
-
const ext =
|
|
23476
|
+
const resolved = path17.resolve(filePath);
|
|
23477
|
+
const ext = path17.extname(resolved).toLowerCase();
|
|
22386
23478
|
if (RAW_SOURCE_EXTENSIONS.has(ext)) {
|
|
22387
23479
|
return { type: "raw-source-file", inputPath: resolved };
|
|
22388
23480
|
}
|
|
22389
23481
|
if (ext === ".md") {
|
|
22390
23482
|
try {
|
|
22391
|
-
const raw =
|
|
23483
|
+
const raw = fs16.readFileSync(resolved, "utf-8");
|
|
22392
23484
|
const { data } = matter4(raw);
|
|
22393
23485
|
if (isValidMarvinDocument(data, knownTypes)) {
|
|
22394
23486
|
return { type: "marvin-document", inputPath: resolved };
|
|
@@ -22513,9 +23605,9 @@ function executeImportPlan(plan, store, marvinDir, options) {
|
|
|
22513
23605
|
continue;
|
|
22514
23606
|
}
|
|
22515
23607
|
if (item.action === "copy") {
|
|
22516
|
-
const targetDir =
|
|
22517
|
-
|
|
22518
|
-
|
|
23608
|
+
const targetDir = path18.dirname(item.targetPath);
|
|
23609
|
+
fs17.mkdirSync(targetDir, { recursive: true });
|
|
23610
|
+
fs17.copyFileSync(item.sourcePath, item.targetPath);
|
|
22519
23611
|
copied++;
|
|
22520
23612
|
continue;
|
|
22521
23613
|
}
|
|
@@ -22551,19 +23643,19 @@ function formatPlanSummary(plan) {
|
|
|
22551
23643
|
lines.push(`Documents to import: ${imports.length}`);
|
|
22552
23644
|
for (const item of imports) {
|
|
22553
23645
|
const idInfo = item.originalId !== item.newId ? `${item.originalId} \u2192 ${item.newId}` : item.newId ?? item.originalId ?? "";
|
|
22554
|
-
lines.push(` ${idInfo} ${
|
|
23646
|
+
lines.push(` ${idInfo} ${path18.basename(item.sourcePath)}`);
|
|
22555
23647
|
}
|
|
22556
23648
|
}
|
|
22557
23649
|
if (copies.length > 0) {
|
|
22558
23650
|
lines.push(`Files to copy to sources/: ${copies.length}`);
|
|
22559
23651
|
for (const item of copies) {
|
|
22560
|
-
lines.push(` ${
|
|
23652
|
+
lines.push(` ${path18.basename(item.sourcePath)} \u2192 ${path18.basename(item.targetPath)}`);
|
|
22561
23653
|
}
|
|
22562
23654
|
}
|
|
22563
23655
|
if (skips.length > 0) {
|
|
22564
23656
|
lines.push(`Skipped (conflict): ${skips.length}`);
|
|
22565
23657
|
for (const item of skips) {
|
|
22566
|
-
lines.push(` ${item.originalId ??
|
|
23658
|
+
lines.push(` ${item.originalId ?? path18.basename(item.sourcePath)} ${item.reason ?? ""}`);
|
|
22567
23659
|
}
|
|
22568
23660
|
}
|
|
22569
23661
|
if (plan.items.length === 0) {
|
|
@@ -22596,11 +23688,11 @@ function getDirNameForType(store, type) {
|
|
|
22596
23688
|
}
|
|
22597
23689
|
function collectMarvinDocs(dir, knownTypes) {
|
|
22598
23690
|
const docs = [];
|
|
22599
|
-
const files =
|
|
23691
|
+
const files = fs17.readdirSync(dir).filter((f) => f.endsWith(".md"));
|
|
22600
23692
|
for (const file2 of files) {
|
|
22601
|
-
const filePath =
|
|
23693
|
+
const filePath = path18.join(dir, file2);
|
|
22602
23694
|
try {
|
|
22603
|
-
const raw =
|
|
23695
|
+
const raw = fs17.readFileSync(filePath, "utf-8");
|
|
22604
23696
|
const { data, content } = matter5(raw);
|
|
22605
23697
|
if (isValidMarvinDocument(data, knownTypes)) {
|
|
22606
23698
|
docs.push({
|
|
@@ -22656,23 +23748,23 @@ function planDocImports(docs, store, options) {
|
|
|
22656
23748
|
}
|
|
22657
23749
|
function planFromMarvinProject(classification, store, _marvinDir, options) {
|
|
22658
23750
|
let projectDir = classification.inputPath;
|
|
22659
|
-
if (
|
|
22660
|
-
const inner =
|
|
22661
|
-
if (
|
|
23751
|
+
if (path18.basename(projectDir) !== ".marvin") {
|
|
23752
|
+
const inner = path18.join(projectDir, ".marvin");
|
|
23753
|
+
if (fs17.existsSync(inner)) {
|
|
22662
23754
|
projectDir = inner;
|
|
22663
23755
|
}
|
|
22664
23756
|
}
|
|
22665
|
-
const docsDir =
|
|
22666
|
-
if (!
|
|
23757
|
+
const docsDir = path18.join(projectDir, "docs");
|
|
23758
|
+
if (!fs17.existsSync(docsDir)) {
|
|
22667
23759
|
return [];
|
|
22668
23760
|
}
|
|
22669
23761
|
const knownTypes = store.registeredTypes;
|
|
22670
23762
|
const allDocs = [];
|
|
22671
|
-
const subdirs =
|
|
22672
|
-
(d) =>
|
|
23763
|
+
const subdirs = fs17.readdirSync(docsDir).filter(
|
|
23764
|
+
(d) => fs17.statSync(path18.join(docsDir, d)).isDirectory()
|
|
22673
23765
|
);
|
|
22674
23766
|
for (const subdir of subdirs) {
|
|
22675
|
-
const docs = collectMarvinDocs(
|
|
23767
|
+
const docs = collectMarvinDocs(path18.join(docsDir, subdir), knownTypes);
|
|
22676
23768
|
allDocs.push(...docs);
|
|
22677
23769
|
}
|
|
22678
23770
|
return planDocImports(allDocs, store, options);
|
|
@@ -22682,10 +23774,10 @@ function planFromDocsDirectory(classification, store, _marvinDir, options) {
|
|
|
22682
23774
|
const knownTypes = store.registeredTypes;
|
|
22683
23775
|
const allDocs = [];
|
|
22684
23776
|
allDocs.push(...collectMarvinDocs(dir, knownTypes));
|
|
22685
|
-
const entries =
|
|
23777
|
+
const entries = fs17.readdirSync(dir);
|
|
22686
23778
|
for (const entry of entries) {
|
|
22687
|
-
const entryPath =
|
|
22688
|
-
if (
|
|
23779
|
+
const entryPath = path18.join(dir, entry);
|
|
23780
|
+
if (fs17.statSync(entryPath).isDirectory()) {
|
|
22689
23781
|
allDocs.push(...collectMarvinDocs(entryPath, knownTypes));
|
|
22690
23782
|
}
|
|
22691
23783
|
}
|
|
@@ -22694,7 +23786,7 @@ function planFromDocsDirectory(classification, store, _marvinDir, options) {
|
|
|
22694
23786
|
function planFromSingleDocument(classification, store, _marvinDir, options) {
|
|
22695
23787
|
const filePath = classification.inputPath;
|
|
22696
23788
|
const knownTypes = store.registeredTypes;
|
|
22697
|
-
const raw =
|
|
23789
|
+
const raw = fs17.readFileSync(filePath, "utf-8");
|
|
22698
23790
|
const { data, content } = matter5(raw);
|
|
22699
23791
|
if (!isValidMarvinDocument(data, knownTypes)) {
|
|
22700
23792
|
return [];
|
|
@@ -22710,14 +23802,14 @@ function planFromSingleDocument(classification, store, _marvinDir, options) {
|
|
|
22710
23802
|
}
|
|
22711
23803
|
function planFromRawSourceDir(classification, marvinDir) {
|
|
22712
23804
|
const dir = classification.inputPath;
|
|
22713
|
-
const sourcesDir =
|
|
23805
|
+
const sourcesDir = path18.join(marvinDir, "sources");
|
|
22714
23806
|
const items = [];
|
|
22715
|
-
const files =
|
|
22716
|
-
const stat =
|
|
23807
|
+
const files = fs17.readdirSync(dir).filter((f) => {
|
|
23808
|
+
const stat = fs17.statSync(path18.join(dir, f));
|
|
22717
23809
|
return stat.isFile();
|
|
22718
23810
|
});
|
|
22719
23811
|
for (const file2 of files) {
|
|
22720
|
-
const sourcePath =
|
|
23812
|
+
const sourcePath = path18.join(dir, file2);
|
|
22721
23813
|
const targetPath = resolveSourceFileName(sourcesDir, file2);
|
|
22722
23814
|
items.push({
|
|
22723
23815
|
action: "copy",
|
|
@@ -22728,8 +23820,8 @@ function planFromRawSourceDir(classification, marvinDir) {
|
|
|
22728
23820
|
return items;
|
|
22729
23821
|
}
|
|
22730
23822
|
function planFromRawSourceFile(classification, marvinDir) {
|
|
22731
|
-
const sourcesDir =
|
|
22732
|
-
const fileName =
|
|
23823
|
+
const sourcesDir = path18.join(marvinDir, "sources");
|
|
23824
|
+
const fileName = path18.basename(classification.inputPath);
|
|
22733
23825
|
const targetPath = resolveSourceFileName(sourcesDir, fileName);
|
|
22734
23826
|
return [
|
|
22735
23827
|
{
|
|
@@ -22740,25 +23832,25 @@ function planFromRawSourceFile(classification, marvinDir) {
|
|
|
22740
23832
|
];
|
|
22741
23833
|
}
|
|
22742
23834
|
function resolveSourceFileName(sourcesDir, fileName) {
|
|
22743
|
-
const targetPath =
|
|
22744
|
-
if (!
|
|
23835
|
+
const targetPath = path18.join(sourcesDir, fileName);
|
|
23836
|
+
if (!fs17.existsSync(targetPath)) {
|
|
22745
23837
|
return targetPath;
|
|
22746
23838
|
}
|
|
22747
|
-
const ext =
|
|
22748
|
-
const base =
|
|
23839
|
+
const ext = path18.extname(fileName);
|
|
23840
|
+
const base = path18.basename(fileName, ext);
|
|
22749
23841
|
let counter = 1;
|
|
22750
23842
|
let candidate;
|
|
22751
23843
|
do {
|
|
22752
|
-
candidate =
|
|
23844
|
+
candidate = path18.join(sourcesDir, `${base}-${counter}${ext}`);
|
|
22753
23845
|
counter++;
|
|
22754
|
-
} while (
|
|
23846
|
+
} while (fs17.existsSync(candidate));
|
|
22755
23847
|
return candidate;
|
|
22756
23848
|
}
|
|
22757
23849
|
|
|
22758
23850
|
// src/cli/commands/import.ts
|
|
22759
23851
|
async function importCommand(inputPath, options) {
|
|
22760
|
-
const resolved =
|
|
22761
|
-
if (!
|
|
23852
|
+
const resolved = path19.resolve(inputPath);
|
|
23853
|
+
if (!fs18.existsSync(resolved)) {
|
|
22762
23854
|
throw new ImportError(`Path not found: ${resolved}`);
|
|
22763
23855
|
}
|
|
22764
23856
|
const project = loadProject();
|
|
@@ -22810,7 +23902,7 @@ async function importCommand(inputPath, options) {
|
|
|
22810
23902
|
console.log(chalk11.bold("\nStarting ingest of copied sources...\n"));
|
|
22811
23903
|
const manifest = new SourceManifestManager(marvinDir);
|
|
22812
23904
|
manifest.scan();
|
|
22813
|
-
const copiedFileNames = result.items.filter((i) => i.action === "copy").map((i) =>
|
|
23905
|
+
const copiedFileNames = result.items.filter((i) => i.action === "copy").map((i) => path19.basename(i.targetPath));
|
|
22814
23906
|
for (const fileName of copiedFileNames) {
|
|
22815
23907
|
try {
|
|
22816
23908
|
await ingestFile({
|
|
@@ -23699,14 +24791,14 @@ async function webCommand(options) {
|
|
|
23699
24791
|
}
|
|
23700
24792
|
|
|
23701
24793
|
// src/cli/commands/generate.ts
|
|
23702
|
-
import * as
|
|
23703
|
-
import * as
|
|
24794
|
+
import * as fs19 from "fs";
|
|
24795
|
+
import * as path20 from "path";
|
|
23704
24796
|
import chalk18 from "chalk";
|
|
23705
24797
|
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
23706
24798
|
async function generateClaudeMdCommand(options) {
|
|
23707
24799
|
const project = loadProject();
|
|
23708
|
-
const filePath =
|
|
23709
|
-
if (
|
|
24800
|
+
const filePath = path20.join(project.marvinDir, "CLAUDE.md");
|
|
24801
|
+
if (fs19.existsSync(filePath) && !options.force) {
|
|
23710
24802
|
const overwrite = await confirm2({
|
|
23711
24803
|
message: ".marvin/CLAUDE.md already exists. Overwrite?",
|
|
23712
24804
|
default: false
|
|
@@ -23716,7 +24808,7 @@ async function generateClaudeMdCommand(options) {
|
|
|
23716
24808
|
return;
|
|
23717
24809
|
}
|
|
23718
24810
|
}
|
|
23719
|
-
|
|
24811
|
+
fs19.writeFileSync(
|
|
23720
24812
|
filePath,
|
|
23721
24813
|
getDefaultClaudeMdContent(project.config.name),
|
|
23722
24814
|
"utf-8"
|
|
@@ -23729,7 +24821,7 @@ function createProgram() {
|
|
|
23729
24821
|
const program = new Command();
|
|
23730
24822
|
program.name("marvin").description(
|
|
23731
24823
|
"AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
|
|
23732
|
-
).version("0.4.
|
|
24824
|
+
).version("0.4.4");
|
|
23733
24825
|
program.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
|
|
23734
24826
|
await initCommand();
|
|
23735
24827
|
});
|