mrvn-cli 0.4.1 → 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 +1436 -323
- package/dist/index.js.map +1 -1
- package/dist/marvin-serve.js +1328 -215
- package/dist/marvin-serve.js.map +1 -1
- package/dist/marvin.js +1440 -327
- 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,12 +14999,19 @@ 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) {
|
|
15005
15006
|
if (value === void 0 || value === null) return [];
|
|
15006
|
-
if (typeof value === "string")
|
|
15007
|
+
if (typeof value === "string") {
|
|
15008
|
+
try {
|
|
15009
|
+
const parsed = JSON.parse(value);
|
|
15010
|
+
if (Array.isArray(parsed)) return parsed.filter((v) => typeof v === "string");
|
|
15011
|
+
} catch {
|
|
15012
|
+
}
|
|
15013
|
+
return [value];
|
|
15014
|
+
}
|
|
15007
15015
|
if (Array.isArray(value)) return value.filter((v) => typeof v === "string");
|
|
15008
15016
|
return [];
|
|
15009
15017
|
}
|
|
@@ -15620,6 +15628,7 @@ function inline(text) {
|
|
|
15620
15628
|
function layout(opts, body) {
|
|
15621
15629
|
const topItems = [
|
|
15622
15630
|
{ href: "/", label: "Overview" },
|
|
15631
|
+
{ href: "/timeline", label: "Timeline" },
|
|
15623
15632
|
{ href: "/board", label: "Board" },
|
|
15624
15633
|
{ href: "/gar", label: "GAR Report" },
|
|
15625
15634
|
{ href: "/health", label: "Health" }
|
|
@@ -15656,7 +15665,7 @@ function layout(opts, body) {
|
|
|
15656
15665
|
${groupsHtml}
|
|
15657
15666
|
</nav>
|
|
15658
15667
|
</aside>
|
|
15659
|
-
<main class="main">
|
|
15668
|
+
<main class="main${opts.mainClass ? ` ${opts.mainClass}` : ""}">
|
|
15660
15669
|
<button class="expand-toggle" onclick="document.querySelector('.main').classList.toggle('expanded')" title="Toggle wide view">
|
|
15661
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>
|
|
15662
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>
|
|
@@ -15665,7 +15674,36 @@ function layout(opts, body) {
|
|
|
15665
15674
|
</main>
|
|
15666
15675
|
</div>
|
|
15667
15676
|
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
|
|
15668
|
-
<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>
|
|
15669
15707
|
</body>
|
|
15670
15708
|
</html>`;
|
|
15671
15709
|
}
|
|
@@ -15912,6 +15950,10 @@ a:hover { text-decoration: underline; }
|
|
|
15912
15950
|
/* Table */
|
|
15913
15951
|
.table-wrap {
|
|
15914
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);
|
|
15915
15957
|
}
|
|
15916
15958
|
|
|
15917
15959
|
table {
|
|
@@ -15927,6 +15969,10 @@ th {
|
|
|
15927
15969
|
letter-spacing: 0.05em;
|
|
15928
15970
|
color: var(--text-dim);
|
|
15929
15971
|
border-bottom: 1px solid var(--border);
|
|
15972
|
+
position: sticky;
|
|
15973
|
+
top: 0;
|
|
15974
|
+
background: var(--bg-card);
|
|
15975
|
+
z-index: 1;
|
|
15930
15976
|
}
|
|
15931
15977
|
|
|
15932
15978
|
td {
|
|
@@ -15974,6 +16020,8 @@ tr:hover td {
|
|
|
15974
16020
|
border: 1px solid var(--border);
|
|
15975
16021
|
border-radius: var(--radius);
|
|
15976
16022
|
padding: 1.25rem;
|
|
16023
|
+
display: flex;
|
|
16024
|
+
flex-direction: column;
|
|
15977
16025
|
}
|
|
15978
16026
|
|
|
15979
16027
|
.gar-area .area-header {
|
|
@@ -16004,6 +16052,9 @@ tr:hover td {
|
|
|
16004
16052
|
.gar-area ul {
|
|
16005
16053
|
list-style: none;
|
|
16006
16054
|
font-size: 0.8rem;
|
|
16055
|
+
max-height: 200px;
|
|
16056
|
+
overflow-y: auto;
|
|
16057
|
+
scrollbar-width: thin;
|
|
16007
16058
|
}
|
|
16008
16059
|
|
|
16009
16060
|
.gar-area li {
|
|
@@ -16026,13 +16077,14 @@ tr:hover td {
|
|
|
16026
16077
|
display: flex;
|
|
16027
16078
|
gap: 1rem;
|
|
16028
16079
|
overflow-x: auto;
|
|
16080
|
+
scrollbar-width: thin;
|
|
16029
16081
|
padding-bottom: 1rem;
|
|
16030
16082
|
}
|
|
16031
16083
|
|
|
16032
16084
|
.board-column {
|
|
16033
16085
|
min-width: 240px;
|
|
16034
16086
|
max-width: 300px;
|
|
16035
|
-
flex:
|
|
16087
|
+
flex: 0 0 auto;
|
|
16036
16088
|
}
|
|
16037
16089
|
|
|
16038
16090
|
.board-column-header {
|
|
@@ -16045,6 +16097,7 @@ tr:hover td {
|
|
|
16045
16097
|
margin-bottom: 0.5rem;
|
|
16046
16098
|
display: flex;
|
|
16047
16099
|
justify-content: space-between;
|
|
16100
|
+
flex-shrink: 0;
|
|
16048
16101
|
}
|
|
16049
16102
|
|
|
16050
16103
|
.board-column-header .count {
|
|
@@ -16226,6 +16279,241 @@ tr:hover td {
|
|
|
16226
16279
|
.mermaid-row .mermaid-container {
|
|
16227
16280
|
margin: 0;
|
|
16228
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
|
+
}
|
|
16229
16517
|
`;
|
|
16230
16518
|
}
|
|
16231
16519
|
|
|
@@ -16234,98 +16522,275 @@ function sanitize(text, maxLen = 40) {
|
|
|
16234
16522
|
const cleaned = text.replace(/["'`]/g, "").replace(/[\r\n]+/g, " ");
|
|
16235
16523
|
return cleaned.length > maxLen ? cleaned.slice(0, maxLen - 1) + "\u2026" : cleaned;
|
|
16236
16524
|
}
|
|
16237
|
-
function mermaidBlock(definition) {
|
|
16238
|
-
|
|
16525
|
+
function mermaidBlock(definition, extraClass) {
|
|
16526
|
+
const cls = ["mermaid-container", extraClass].filter(Boolean).join(" ");
|
|
16527
|
+
return `<div class="${cls}"><pre class="mermaid">
|
|
16239
16528
|
${definition}
|
|
16240
16529
|
</pre></div>`;
|
|
16241
16530
|
}
|
|
16242
16531
|
function placeholder(message) {
|
|
16243
16532
|
return `<div class="mermaid-container mermaid-empty"><p>${message}</p></div>`;
|
|
16244
16533
|
}
|
|
16245
|
-
function
|
|
16246
|
-
|
|
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);
|
|
16247
16544
|
if (sprintsWithDates.length === 0) {
|
|
16248
16545
|
return placeholder("No timeline data available \u2014 sprints need start and end dates.");
|
|
16249
16546
|
}
|
|
16547
|
+
const truncated = sprintsWithDates.length > maxSprints;
|
|
16548
|
+
const visibleSprints = truncated ? sprintsWithDates.slice(-maxSprints) : sprintsWithDates;
|
|
16549
|
+
const hiddenCount = sprintsWithDates.length - visibleSprints.length;
|
|
16250
16550
|
const epicMap = new Map(data.epics.map((e) => [e.id, e]));
|
|
16251
|
-
const
|
|
16252
|
-
|
|
16253
|
-
|
|
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>`);
|
|
16254
16584
|
const linked = sprint.linkedEpics.map((eid) => epicMap.get(eid)).filter(Boolean);
|
|
16255
|
-
|
|
16256
|
-
|
|
16257
|
-
|
|
16258
|
-
|
|
16259
|
-
|
|
16260
|
-
|
|
16261
|
-
|
|
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>`);
|
|
16262
16596
|
}
|
|
16263
16597
|
}
|
|
16264
|
-
|
|
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";
|
|
16265
16620
|
}
|
|
16266
16621
|
function buildArtifactFlowchart(data) {
|
|
16267
16622
|
if (data.features.length === 0 && data.epics.length === 0) {
|
|
16268
16623
|
return placeholder("No artifact relationships found \u2014 create features and epics to see the hierarchy.");
|
|
16269
16624
|
}
|
|
16270
|
-
const
|
|
16271
|
-
|
|
16272
|
-
lines.push(" classDef inprogress fill:#78350f,stroke:#fbbf24,color:#fef3c7");
|
|
16273
|
-
lines.push(" classDef blocked fill:#7f1d1d,stroke:#f87171,color:#fee2e2");
|
|
16274
|
-
lines.push(" classDef default fill:#1e293b,stroke:#475569,color:#e2e8f0");
|
|
16275
|
-
const nodeIds = /* @__PURE__ */ new Set();
|
|
16625
|
+
const edges = [];
|
|
16626
|
+
const epicsByFeature = /* @__PURE__ */ new Map();
|
|
16276
16627
|
for (const epic of data.epics) {
|
|
16277
|
-
for (const
|
|
16278
|
-
|
|
16279
|
-
|
|
16280
|
-
|
|
16281
|
-
const eNode = epic.id.replace(/-/g, "_");
|
|
16282
|
-
if (!nodeIds.has(fNode)) {
|
|
16283
|
-
lines.push(` ${fNode}["${sanitize(feature.id + " " + feature.title)}"]`);
|
|
16284
|
-
nodeIds.add(fNode);
|
|
16285
|
-
}
|
|
16286
|
-
if (!nodeIds.has(eNode)) {
|
|
16287
|
-
lines.push(` ${eNode}["${sanitize(epic.id + " " + epic.title)}"]`);
|
|
16288
|
-
nodeIds.add(eNode);
|
|
16289
|
-
}
|
|
16290
|
-
lines.push(` ${fNode} --> ${eNode}`);
|
|
16291
|
-
}
|
|
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 });
|
|
16292
16632
|
}
|
|
16293
16633
|
}
|
|
16634
|
+
const sprintsByEpic = /* @__PURE__ */ new Map();
|
|
16294
16635
|
for (const sprint of data.sprints) {
|
|
16295
|
-
const
|
|
16296
|
-
|
|
16297
|
-
|
|
16298
|
-
|
|
16299
|
-
|
|
16300
|
-
|
|
16301
|
-
|
|
16302
|
-
|
|
16303
|
-
|
|
16304
|
-
|
|
16305
|
-
|
|
16306
|
-
|
|
16307
|
-
|
|
16308
|
-
|
|
16309
|
-
|
|
16310
|
-
|
|
16311
|
-
|
|
16312
|
-
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) {
|
|
16313
16654
|
return placeholder("No artifact relationships found \u2014 link epics to features and sprints.");
|
|
16314
16655
|
}
|
|
16315
|
-
const
|
|
16316
|
-
|
|
16317
|
-
|
|
16318
|
-
|
|
16319
|
-
|
|
16320
|
-
|
|
16321
|
-
|
|
16322
|
-
|
|
16323
|
-
|
|
16324
|
-
|
|
16325
|
-
lines
|
|
16326
|
-
|
|
16327
|
-
|
|
16328
|
-
|
|
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>`;
|
|
16329
16794
|
}
|
|
16330
16795
|
function buildStatusPie(title, counts) {
|
|
16331
16796
|
const entries = Object.entries(counts).filter(([, v]) => v > 0);
|
|
@@ -16405,8 +16870,7 @@ function overviewPage(data, diagrams, navGroups) {
|
|
|
16405
16870
|
${groupSections}
|
|
16406
16871
|
${ungroupedSection}
|
|
16407
16872
|
|
|
16408
|
-
<div class="section-title">Project Timeline
|
|
16409
|
-
${buildTimelineGantt(diagrams)}
|
|
16873
|
+
<div class="section-title"><a href="/timeline">Project Timeline →</a></div>
|
|
16410
16874
|
|
|
16411
16875
|
<div class="section-title">Artifact Relationships</div>
|
|
16412
16876
|
${buildArtifactFlowchart(diagrams)}
|
|
@@ -16661,6 +17125,7 @@ function boardPage(data) {
|
|
|
16661
17125
|
<span>${escapeHtml(col.status)}</span>
|
|
16662
17126
|
<span class="count">${col.docs.length}</span>
|
|
16663
17127
|
</div>
|
|
17128
|
+
<div class="board-column-cards">
|
|
16664
17129
|
${col.docs.map(
|
|
16665
17130
|
(doc) => `
|
|
16666
17131
|
<div class="board-card">
|
|
@@ -16671,6 +17136,7 @@ function boardPage(data) {
|
|
|
16671
17136
|
</a>
|
|
16672
17137
|
</div>`
|
|
16673
17138
|
).join("\n")}
|
|
17139
|
+
</div>
|
|
16674
17140
|
</div>`
|
|
16675
17141
|
).join("\n");
|
|
16676
17142
|
return `
|
|
@@ -16696,6 +17162,18 @@ function boardPage(data) {
|
|
|
16696
17162
|
`;
|
|
16697
17163
|
}
|
|
16698
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
|
+
|
|
16699
17177
|
// src/web/router.ts
|
|
16700
17178
|
function handleRequest(req, res, store, projectName, navGroups) {
|
|
16701
17179
|
const parsed = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
@@ -16717,6 +17195,12 @@ function handleRequest(req, res, store, projectName, navGroups) {
|
|
|
16717
17195
|
respond(res, layout({ title: "Overview", activePath: "/", projectName, navGroups }, body));
|
|
16718
17196
|
return;
|
|
16719
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
|
+
}
|
|
16720
17204
|
if (pathname === "/gar") {
|
|
16721
17205
|
const report = getGarData(store, projectName);
|
|
16722
17206
|
const body = garPage(report);
|
|
@@ -17408,6 +17892,20 @@ function createFeatureTools(store) {
|
|
|
17408
17892
|
|
|
17409
17893
|
// src/plugins/builtin/tools/epics.ts
|
|
17410
17894
|
import { tool as tool10 } from "@anthropic-ai/claude-agent-sdk";
|
|
17895
|
+
var linkedFeatureArray = external_exports.preprocess(
|
|
17896
|
+
(val) => {
|
|
17897
|
+
if (typeof val === "string") {
|
|
17898
|
+
try {
|
|
17899
|
+
const parsed = JSON.parse(val);
|
|
17900
|
+
if (Array.isArray(parsed)) return parsed;
|
|
17901
|
+
} catch {
|
|
17902
|
+
}
|
|
17903
|
+
return [val];
|
|
17904
|
+
}
|
|
17905
|
+
return val;
|
|
17906
|
+
},
|
|
17907
|
+
external_exports.array(external_exports.string())
|
|
17908
|
+
);
|
|
17411
17909
|
function createEpicTools(store) {
|
|
17412
17910
|
return [
|
|
17413
17911
|
tool10(
|
|
@@ -17473,7 +17971,7 @@ function createEpicTools(store) {
|
|
|
17473
17971
|
{
|
|
17474
17972
|
title: external_exports.string().describe("Epic title"),
|
|
17475
17973
|
content: external_exports.string().describe("Epic description and scope"),
|
|
17476
|
-
linkedFeature:
|
|
17974
|
+
linkedFeature: linkedFeatureArray.describe("Feature ID(s) to link this epic to (e.g. ['F-001'] or ['F-001', 'F-002'])"),
|
|
17477
17975
|
status: external_exports.enum(["planned", "in-progress", "done"]).optional().describe("Epic status (default: 'planned')"),
|
|
17478
17976
|
owner: external_exports.string().optional().describe("Epic owner"),
|
|
17479
17977
|
targetDate: external_exports.string().optional().describe("Target completion date (ISO format)"),
|
|
@@ -17549,7 +18047,7 @@ function createEpicTools(store) {
|
|
|
17549
18047
|
owner: external_exports.string().optional().describe("New owner"),
|
|
17550
18048
|
targetDate: external_exports.string().optional().describe("New target date"),
|
|
17551
18049
|
estimatedEffort: external_exports.string().optional().describe("New estimated effort"),
|
|
17552
|
-
linkedFeature:
|
|
18050
|
+
linkedFeature: linkedFeatureArray.optional().describe("New linked feature ID(s)"),
|
|
17553
18051
|
tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove 'risk', add 'risk-mitigated')")
|
|
17554
18052
|
},
|
|
17555
18053
|
async (args) => {
|
|
@@ -18086,6 +18584,205 @@ function createSprintPlanningTools(store) {
|
|
|
18086
18584
|
];
|
|
18087
18585
|
}
|
|
18088
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
|
+
|
|
18089
18786
|
// src/plugins/common.ts
|
|
18090
18787
|
var COMMON_REGISTRATIONS = [
|
|
18091
18788
|
{ type: "meeting", dirName: "meetings", idPrefix: "M" },
|
|
@@ -18093,7 +18790,8 @@ var COMMON_REGISTRATIONS = [
|
|
|
18093
18790
|
{ type: "feature", dirName: "features", idPrefix: "F" },
|
|
18094
18791
|
{ type: "epic", dirName: "epics", idPrefix: "E" },
|
|
18095
18792
|
{ type: "contribution", dirName: "contributions", idPrefix: "C" },
|
|
18096
|
-
{ type: "sprint", dirName: "sprints", idPrefix: "SP" }
|
|
18793
|
+
{ type: "sprint", dirName: "sprints", idPrefix: "SP" },
|
|
18794
|
+
{ type: "task", dirName: "tasks", idPrefix: "T" }
|
|
18097
18795
|
];
|
|
18098
18796
|
function createCommonTools(store) {
|
|
18099
18797
|
return [
|
|
@@ -18103,7 +18801,8 @@ function createCommonTools(store) {
|
|
|
18103
18801
|
...createEpicTools(store),
|
|
18104
18802
|
...createContributionTools(store),
|
|
18105
18803
|
...createSprintTools(store),
|
|
18106
|
-
...createSprintPlanningTools(store)
|
|
18804
|
+
...createSprintPlanningTools(store),
|
|
18805
|
+
...createTaskTools(store)
|
|
18107
18806
|
];
|
|
18108
18807
|
}
|
|
18109
18808
|
|
|
@@ -18113,7 +18812,7 @@ var genericAgilePlugin = {
|
|
|
18113
18812
|
name: "Generic Agile",
|
|
18114
18813
|
description: "Default methodology plugin providing standard agile governance patterns for decisions, actions, and questions.",
|
|
18115
18814
|
version: "0.1.0",
|
|
18116
|
-
documentTypes: ["decision", "action", "question", "meeting", "report", "feature", "epic", "contribution", "sprint"],
|
|
18815
|
+
documentTypes: ["decision", "action", "question", "meeting", "report", "feature", "epic", "contribution", "sprint", "task"],
|
|
18117
18816
|
documentTypeRegistrations: [...COMMON_REGISTRATIONS],
|
|
18118
18817
|
tools: (store) => [...createCommonTools(store)],
|
|
18119
18818
|
promptFragments: {
|
|
@@ -18152,6 +18851,11 @@ var genericAgilePlugin = {
|
|
|
18152
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.
|
|
18153
18852
|
- **update_epic**: Update epic status (planned \u2192 in-progress \u2192 done), owner, and other fields.
|
|
18154
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
|
+
|
|
18155
18859
|
**Feature Tools (read-only for awareness):**
|
|
18156
18860
|
- **list_features** / **get_feature**: View features to understand what needs to be broken into epics.
|
|
18157
18861
|
|
|
@@ -18163,6 +18867,7 @@ var genericAgilePlugin = {
|
|
|
18163
18867
|
|
|
18164
18868
|
**Key Workflow Rules:**
|
|
18165
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.
|
|
18166
18871
|
- Tag work items (actions, decisions, questions) with \`epic:E-xxx\` to group them under an epic.
|
|
18167
18872
|
- Collaborate with the Delivery Manager on target dates and effort estimates.
|
|
18168
18873
|
- Each epic should have a clear scope and definition of done.
|
|
@@ -18198,6 +18903,9 @@ var genericAgilePlugin = {
|
|
|
18198
18903
|
- **list_epics** / **get_epic**: View epics and their current status.
|
|
18199
18904
|
- **update_epic**: Set targetDate and estimatedEffort on epics. Flag epics linked to deferred features.
|
|
18200
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
|
+
|
|
18201
18909
|
**Feature Tools (tracking focus):**
|
|
18202
18910
|
- **list_features** / **get_feature**: View features and their priorities.
|
|
18203
18911
|
|
|
@@ -18243,14 +18951,15 @@ var genericAgilePlugin = {
|
|
|
18243
18951
|
- Reason through: priority (critical/high features first), capacity (compare backlog effort to velocity reference), dependencies and blockers, balance across features, and risk.
|
|
18244
18952
|
- Present a structured sprint proposal: title, goal, suggested dates, selected epics with rationale for each, excluded epics with reason, and identified risks.
|
|
18245
18953
|
- After user confirmation, use **create_sprint** with the agreed epics to persist the sprint.`,
|
|
18246
|
-
"*": `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:
|
|
18247
18955
|
|
|
18248
18956
|
**Features** (F-xxx): Product capabilities defined by the Product Owner. Features progress through draft \u2192 approved \u2192 done.
|
|
18249
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.
|
|
18250
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).
|
|
18251
18960
|
**Meetings**: Meeting records with attendees, agendas, and notes.
|
|
18252
18961
|
|
|
18253
|
-
**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.
|
|
18254
18963
|
|
|
18255
18964
|
- **list_meetings** / **get_meeting**: Browse and read meeting records.
|
|
18256
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.
|
|
@@ -18265,10 +18974,10 @@ var genericAgilePlugin = {
|
|
|
18265
18974
|
};
|
|
18266
18975
|
|
|
18267
18976
|
// src/plugins/builtin/tools/use-cases.ts
|
|
18268
|
-
import { tool as
|
|
18977
|
+
import { tool as tool15 } from "@anthropic-ai/claude-agent-sdk";
|
|
18269
18978
|
function createUseCaseTools(store) {
|
|
18270
18979
|
return [
|
|
18271
|
-
|
|
18980
|
+
tool15(
|
|
18272
18981
|
"list_use_cases",
|
|
18273
18982
|
"List all extension use cases, optionally filtered by status or extension type",
|
|
18274
18983
|
{
|
|
@@ -18298,7 +19007,7 @@ function createUseCaseTools(store) {
|
|
|
18298
19007
|
},
|
|
18299
19008
|
{ annotations: { readOnlyHint: true } }
|
|
18300
19009
|
),
|
|
18301
|
-
|
|
19010
|
+
tool15(
|
|
18302
19011
|
"get_use_case",
|
|
18303
19012
|
"Get the full content of a specific use case by ID",
|
|
18304
19013
|
{ id: external_exports.string().describe("Use case ID (e.g. 'UC-001')") },
|
|
@@ -18325,7 +19034,7 @@ function createUseCaseTools(store) {
|
|
|
18325
19034
|
},
|
|
18326
19035
|
{ annotations: { readOnlyHint: true } }
|
|
18327
19036
|
),
|
|
18328
|
-
|
|
19037
|
+
tool15(
|
|
18329
19038
|
"create_use_case",
|
|
18330
19039
|
"Create a new extension use case definition (Phase 1: Assess Extension Use Case)",
|
|
18331
19040
|
{
|
|
@@ -18359,7 +19068,7 @@ function createUseCaseTools(store) {
|
|
|
18359
19068
|
};
|
|
18360
19069
|
}
|
|
18361
19070
|
),
|
|
18362
|
-
|
|
19071
|
+
tool15(
|
|
18363
19072
|
"update_use_case",
|
|
18364
19073
|
"Update an existing extension use case",
|
|
18365
19074
|
{
|
|
@@ -18389,10 +19098,10 @@ function createUseCaseTools(store) {
|
|
|
18389
19098
|
}
|
|
18390
19099
|
|
|
18391
19100
|
// src/plugins/builtin/tools/tech-assessments.ts
|
|
18392
|
-
import { tool as
|
|
19101
|
+
import { tool as tool16 } from "@anthropic-ai/claude-agent-sdk";
|
|
18393
19102
|
function createTechAssessmentTools(store) {
|
|
18394
19103
|
return [
|
|
18395
|
-
|
|
19104
|
+
tool16(
|
|
18396
19105
|
"list_tech_assessments",
|
|
18397
19106
|
"List all technology assessments, optionally filtered by status",
|
|
18398
19107
|
{
|
|
@@ -18423,7 +19132,7 @@ function createTechAssessmentTools(store) {
|
|
|
18423
19132
|
},
|
|
18424
19133
|
{ annotations: { readOnlyHint: true } }
|
|
18425
19134
|
),
|
|
18426
|
-
|
|
19135
|
+
tool16(
|
|
18427
19136
|
"get_tech_assessment",
|
|
18428
19137
|
"Get the full content of a specific technology assessment by ID",
|
|
18429
19138
|
{ id: external_exports.string().describe("Tech assessment ID (e.g. 'TA-001')") },
|
|
@@ -18450,7 +19159,7 @@ function createTechAssessmentTools(store) {
|
|
|
18450
19159
|
},
|
|
18451
19160
|
{ annotations: { readOnlyHint: true } }
|
|
18452
19161
|
),
|
|
18453
|
-
|
|
19162
|
+
tool16(
|
|
18454
19163
|
"create_tech_assessment",
|
|
18455
19164
|
"Create a new technology assessment linked to an assessed/approved use case (Phase 2: Assess Extension Technology)",
|
|
18456
19165
|
{
|
|
@@ -18521,7 +19230,7 @@ function createTechAssessmentTools(store) {
|
|
|
18521
19230
|
};
|
|
18522
19231
|
}
|
|
18523
19232
|
),
|
|
18524
|
-
|
|
19233
|
+
tool16(
|
|
18525
19234
|
"update_tech_assessment",
|
|
18526
19235
|
"Update an existing technology assessment. The linked use case cannot be changed.",
|
|
18527
19236
|
{
|
|
@@ -18551,10 +19260,10 @@ function createTechAssessmentTools(store) {
|
|
|
18551
19260
|
}
|
|
18552
19261
|
|
|
18553
19262
|
// src/plugins/builtin/tools/extension-designs.ts
|
|
18554
|
-
import { tool as
|
|
19263
|
+
import { tool as tool17 } from "@anthropic-ai/claude-agent-sdk";
|
|
18555
19264
|
function createExtensionDesignTools(store) {
|
|
18556
19265
|
return [
|
|
18557
|
-
|
|
19266
|
+
tool17(
|
|
18558
19267
|
"list_extension_designs",
|
|
18559
19268
|
"List all extension designs, optionally filtered by status",
|
|
18560
19269
|
{
|
|
@@ -18584,7 +19293,7 @@ function createExtensionDesignTools(store) {
|
|
|
18584
19293
|
},
|
|
18585
19294
|
{ annotations: { readOnlyHint: true } }
|
|
18586
19295
|
),
|
|
18587
|
-
|
|
19296
|
+
tool17(
|
|
18588
19297
|
"get_extension_design",
|
|
18589
19298
|
"Get the full content of a specific extension design by ID",
|
|
18590
19299
|
{ id: external_exports.string().describe("Extension design ID (e.g. 'XD-001')") },
|
|
@@ -18611,7 +19320,7 @@ function createExtensionDesignTools(store) {
|
|
|
18611
19320
|
},
|
|
18612
19321
|
{ annotations: { readOnlyHint: true } }
|
|
18613
19322
|
),
|
|
18614
|
-
|
|
19323
|
+
tool17(
|
|
18615
19324
|
"create_extension_design",
|
|
18616
19325
|
"Create a new extension design linked to a recommended tech assessment (Phase 3: Define Extension Target Solution)",
|
|
18617
19326
|
{
|
|
@@ -18679,7 +19388,7 @@ function createExtensionDesignTools(store) {
|
|
|
18679
19388
|
};
|
|
18680
19389
|
}
|
|
18681
19390
|
),
|
|
18682
|
-
|
|
19391
|
+
tool17(
|
|
18683
19392
|
"update_extension_design",
|
|
18684
19393
|
"Update an existing extension design. The linked tech assessment cannot be changed.",
|
|
18685
19394
|
{
|
|
@@ -18708,10 +19417,10 @@ function createExtensionDesignTools(store) {
|
|
|
18708
19417
|
}
|
|
18709
19418
|
|
|
18710
19419
|
// src/plugins/builtin/tools/aem-reports.ts
|
|
18711
|
-
import { tool as
|
|
19420
|
+
import { tool as tool18 } from "@anthropic-ai/claude-agent-sdk";
|
|
18712
19421
|
function createAemReportTools(store) {
|
|
18713
19422
|
return [
|
|
18714
|
-
|
|
19423
|
+
tool18(
|
|
18715
19424
|
"generate_extension_portfolio",
|
|
18716
19425
|
"Generate a portfolio view of all use cases with their linked tech assessments and extension designs",
|
|
18717
19426
|
{},
|
|
@@ -18763,7 +19472,7 @@ function createAemReportTools(store) {
|
|
|
18763
19472
|
},
|
|
18764
19473
|
{ annotations: { readOnlyHint: true } }
|
|
18765
19474
|
),
|
|
18766
|
-
|
|
19475
|
+
tool18(
|
|
18767
19476
|
"generate_tech_readiness",
|
|
18768
19477
|
"Generate a BTP technology readiness report showing service coverage and gaps across assessments",
|
|
18769
19478
|
{},
|
|
@@ -18815,7 +19524,7 @@ function createAemReportTools(store) {
|
|
|
18815
19524
|
},
|
|
18816
19525
|
{ annotations: { readOnlyHint: true } }
|
|
18817
19526
|
),
|
|
18818
|
-
|
|
19527
|
+
tool18(
|
|
18819
19528
|
"generate_phase_status",
|
|
18820
19529
|
"Generate a phase progress report showing artifact counts and readiness per AEM phase",
|
|
18821
19530
|
{},
|
|
@@ -18877,11 +19586,11 @@ function createAemReportTools(store) {
|
|
|
18877
19586
|
import * as fs5 from "fs";
|
|
18878
19587
|
import * as path5 from "path";
|
|
18879
19588
|
import * as YAML2 from "yaml";
|
|
18880
|
-
import { tool as
|
|
19589
|
+
import { tool as tool19 } from "@anthropic-ai/claude-agent-sdk";
|
|
18881
19590
|
var PHASES = ["assess-use-case", "assess-technology", "define-solution"];
|
|
18882
19591
|
function createAemPhaseTools(store, marvinDir) {
|
|
18883
19592
|
return [
|
|
18884
|
-
|
|
19593
|
+
tool19(
|
|
18885
19594
|
"get_current_phase",
|
|
18886
19595
|
"Get the current AEM phase from project configuration",
|
|
18887
19596
|
{},
|
|
@@ -18902,7 +19611,7 @@ function createAemPhaseTools(store, marvinDir) {
|
|
|
18902
19611
|
},
|
|
18903
19612
|
{ annotations: { readOnlyHint: true } }
|
|
18904
19613
|
),
|
|
18905
|
-
|
|
19614
|
+
tool19(
|
|
18906
19615
|
"advance_phase",
|
|
18907
19616
|
"Advance to the next AEM phase. Performs soft gate checks and warns if artifacts are incomplete, but does not block.",
|
|
18908
19617
|
{
|
|
@@ -19166,8 +19875,8 @@ function getPluginPromptFragment(plugin, personaId) {
|
|
|
19166
19875
|
}
|
|
19167
19876
|
|
|
19168
19877
|
// src/skills/registry.ts
|
|
19169
|
-
import * as
|
|
19170
|
-
import * as
|
|
19878
|
+
import * as fs7 from "fs";
|
|
19879
|
+
import * as path7 from "path";
|
|
19171
19880
|
import { fileURLToPath } from "url";
|
|
19172
19881
|
import * as YAML3 from "yaml";
|
|
19173
19882
|
import matter2 from "gray-matter";
|
|
@@ -19210,7 +19919,7 @@ Be thorough but concise. Focus on actionable insights.`,
|
|
|
19210
19919
|
};
|
|
19211
19920
|
|
|
19212
19921
|
// src/skills/builtin/jira/tools.ts
|
|
19213
|
-
import { tool as
|
|
19922
|
+
import { tool as tool20 } from "@anthropic-ai/claude-agent-sdk";
|
|
19214
19923
|
|
|
19215
19924
|
// src/skills/builtin/jira/client.ts
|
|
19216
19925
|
var JiraClient = class {
|
|
@@ -19220,8 +19929,8 @@ var JiraClient = class {
|
|
|
19220
19929
|
this.baseUrl = `https://${config2.host}/rest/api/2`;
|
|
19221
19930
|
this.authHeader = "Basic " + Buffer.from(`${config2.email}:${config2.apiToken}`).toString("base64");
|
|
19222
19931
|
}
|
|
19223
|
-
async request(
|
|
19224
|
-
const url2 = `${this.baseUrl}${
|
|
19932
|
+
async request(path21, method = "GET", body) {
|
|
19933
|
+
const url2 = `${this.baseUrl}${path21}`;
|
|
19225
19934
|
const headers = {
|
|
19226
19935
|
Authorization: this.authHeader,
|
|
19227
19936
|
"Content-Type": "application/json",
|
|
@@ -19235,7 +19944,7 @@ var JiraClient = class {
|
|
|
19235
19944
|
if (!response.ok) {
|
|
19236
19945
|
const text = await response.text().catch(() => "");
|
|
19237
19946
|
throw new Error(
|
|
19238
|
-
`Jira API error ${response.status} ${method} ${
|
|
19947
|
+
`Jira API error ${response.status} ${method} ${path21}: ${text}`
|
|
19239
19948
|
);
|
|
19240
19949
|
}
|
|
19241
19950
|
if (response.status === 204) return void 0;
|
|
@@ -19319,7 +20028,7 @@ function createJiraTools(store) {
|
|
|
19319
20028
|
const jiraUserConfig = loadUserConfig().jira;
|
|
19320
20029
|
return [
|
|
19321
20030
|
// --- Local read tools ---
|
|
19322
|
-
|
|
20031
|
+
tool20(
|
|
19323
20032
|
"list_jira_issues",
|
|
19324
20033
|
"List locally synced Jira issues (JI-xxx documents), optionally filtered by status or Jira key",
|
|
19325
20034
|
{
|
|
@@ -19347,7 +20056,7 @@ function createJiraTools(store) {
|
|
|
19347
20056
|
},
|
|
19348
20057
|
{ annotations: { readOnlyHint: true } }
|
|
19349
20058
|
),
|
|
19350
|
-
|
|
20059
|
+
tool20(
|
|
19351
20060
|
"get_jira_issue",
|
|
19352
20061
|
"Get the full content of a locally synced Jira issue by local ID (JI-xxx) or Jira key (PROJ-123)",
|
|
19353
20062
|
{
|
|
@@ -19380,7 +20089,7 @@ function createJiraTools(store) {
|
|
|
19380
20089
|
{ annotations: { readOnlyHint: true } }
|
|
19381
20090
|
),
|
|
19382
20091
|
// --- Jira → Local tools ---
|
|
19383
|
-
|
|
20092
|
+
tool20(
|
|
19384
20093
|
"pull_jira_issue",
|
|
19385
20094
|
"Fetch a single Jira issue by key and create/update a local JI-xxx document",
|
|
19386
20095
|
{
|
|
@@ -19427,7 +20136,7 @@ function createJiraTools(store) {
|
|
|
19427
20136
|
};
|
|
19428
20137
|
}
|
|
19429
20138
|
),
|
|
19430
|
-
|
|
20139
|
+
tool20(
|
|
19431
20140
|
"pull_jira_issues_jql",
|
|
19432
20141
|
"Bulk fetch Jira issues via JQL query and create/update local JI-xxx documents",
|
|
19433
20142
|
{
|
|
@@ -19475,7 +20184,7 @@ function createJiraTools(store) {
|
|
|
19475
20184
|
}
|
|
19476
20185
|
),
|
|
19477
20186
|
// --- Local → Jira tools ---
|
|
19478
|
-
|
|
20187
|
+
tool20(
|
|
19479
20188
|
"push_artifact_to_jira",
|
|
19480
20189
|
"Create a Jira issue from any Marvin artifact (D/A/Q/F/E) and create a tracking JI-xxx document",
|
|
19481
20190
|
{
|
|
@@ -19536,7 +20245,7 @@ function createJiraTools(store) {
|
|
|
19536
20245
|
}
|
|
19537
20246
|
),
|
|
19538
20247
|
// --- Bidirectional sync ---
|
|
19539
|
-
|
|
20248
|
+
tool20(
|
|
19540
20249
|
"sync_jira_issue",
|
|
19541
20250
|
"Bidirectional sync: push local title/description to Jira, pull latest status/assignee/labels back",
|
|
19542
20251
|
{
|
|
@@ -19577,7 +20286,7 @@ function createJiraTools(store) {
|
|
|
19577
20286
|
}
|
|
19578
20287
|
),
|
|
19579
20288
|
// --- Local link tool ---
|
|
19580
|
-
|
|
20289
|
+
tool20(
|
|
19581
20290
|
"link_artifact_to_jira",
|
|
19582
20291
|
"Add a Marvin artifact ID to a JI-xxx document's linkedArtifacts field",
|
|
19583
20292
|
{
|
|
@@ -19665,13 +20374,13 @@ var jiraSkill = {
|
|
|
19665
20374
|
**Available tools:**
|
|
19666
20375
|
- \`list_jira_issues\` / \`get_jira_issue\` \u2014 browse locally synced Jira issues
|
|
19667
20376
|
- \`pull_jira_issue\` / \`pull_jira_issues_jql\` \u2014 import issues from Jira by key or JQL query
|
|
19668
|
-
- \`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.)
|
|
19669
20378
|
- \`sync_jira_issue\` \u2014 bidirectional sync of a local JI-xxx with Jira
|
|
19670
20379
|
- \`link_artifact_to_jira\` \u2014 link a Marvin artifact to an existing JI-xxx
|
|
19671
20380
|
|
|
19672
20381
|
**As Tech Lead, use Jira integration to:**
|
|
19673
20382
|
- Pull technical issues and bugs for sprint planning and estimation
|
|
19674
|
-
- Push epics and technical decisions to Jira for cross-team visibility
|
|
20383
|
+
- Push epics, tasks, and technical decisions to Jira for cross-team visibility
|
|
19675
20384
|
- Bidirectional sync to keep local governance and Jira in alignment
|
|
19676
20385
|
- Use JQL queries to track technical debt (e.g. \`labels = "tech-debt" AND status != "Done"\`)`,
|
|
19677
20386
|
"delivery-manager": `You have the **Jira Integration** skill. You can pull issues from Jira and push Marvin artifacts to Jira.
|
|
@@ -19685,16 +20394,420 @@ var jiraSkill = {
|
|
|
19685
20394
|
|
|
19686
20395
|
**As Delivery Manager, use Jira integration to:**
|
|
19687
20396
|
- Pull sprint issues for tracking progress and blockers
|
|
19688
|
-
- Push actions and
|
|
20397
|
+
- Push actions, decisions, and tasks to Jira for stakeholder visibility
|
|
19689
20398
|
- Use JQL queries for reporting (e.g. \`sprint in openSprints() AND assignee = currentUser()\`)
|
|
19690
20399
|
- Sync status between Marvin governance items and Jira issues`
|
|
19691
20400
|
}
|
|
19692
20401
|
};
|
|
19693
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
|
+
|
|
19694
20806
|
// src/skills/registry.ts
|
|
19695
20807
|
var BUILTIN_SKILLS = {
|
|
19696
20808
|
"governance-review": governanceReviewSkill,
|
|
19697
|
-
"jira": jiraSkill
|
|
20809
|
+
"jira": jiraSkill,
|
|
20810
|
+
"prd-generator": prdGeneratorSkill
|
|
19698
20811
|
};
|
|
19699
20812
|
var GOVERNANCE_TOOL_NAMES = [
|
|
19700
20813
|
"mcp__marvin-governance__list_decisions",
|
|
@@ -19715,13 +20828,13 @@ var GOVERNANCE_TOOL_NAMES = [
|
|
|
19715
20828
|
];
|
|
19716
20829
|
function getBuiltinSkillsDir() {
|
|
19717
20830
|
const thisFile = fileURLToPath(import.meta.url);
|
|
19718
|
-
return
|
|
20831
|
+
return path7.join(path7.dirname(thisFile), "builtin");
|
|
19719
20832
|
}
|
|
19720
20833
|
function loadSkillFromDirectory(dirPath) {
|
|
19721
|
-
const skillMdPath =
|
|
19722
|
-
if (!
|
|
20834
|
+
const skillMdPath = path7.join(dirPath, "SKILL.md");
|
|
20835
|
+
if (!fs7.existsSync(skillMdPath)) return void 0;
|
|
19723
20836
|
try {
|
|
19724
|
-
const raw =
|
|
20837
|
+
const raw = fs7.readFileSync(skillMdPath, "utf-8");
|
|
19725
20838
|
const { data, content } = matter2(raw);
|
|
19726
20839
|
if (!data.name || !data.description) return void 0;
|
|
19727
20840
|
const metadata = data.metadata ?? {};
|
|
@@ -19732,13 +20845,13 @@ function loadSkillFromDirectory(dirPath) {
|
|
|
19732
20845
|
if (wildcardPrompt) {
|
|
19733
20846
|
promptFragments["*"] = wildcardPrompt;
|
|
19734
20847
|
}
|
|
19735
|
-
const personasDir =
|
|
19736
|
-
if (
|
|
20848
|
+
const personasDir = path7.join(dirPath, "personas");
|
|
20849
|
+
if (fs7.existsSync(personasDir)) {
|
|
19737
20850
|
try {
|
|
19738
|
-
for (const file2 of
|
|
20851
|
+
for (const file2 of fs7.readdirSync(personasDir)) {
|
|
19739
20852
|
if (!file2.endsWith(".md")) continue;
|
|
19740
20853
|
const personaId = file2.replace(/\.md$/, "");
|
|
19741
|
-
const personaPrompt =
|
|
20854
|
+
const personaPrompt = fs7.readFileSync(path7.join(personasDir, file2), "utf-8").trim();
|
|
19742
20855
|
if (personaPrompt) {
|
|
19743
20856
|
promptFragments[personaId] = personaPrompt;
|
|
19744
20857
|
}
|
|
@@ -19747,10 +20860,10 @@ function loadSkillFromDirectory(dirPath) {
|
|
|
19747
20860
|
}
|
|
19748
20861
|
}
|
|
19749
20862
|
let actions;
|
|
19750
|
-
const actionsPath =
|
|
19751
|
-
if (
|
|
20863
|
+
const actionsPath = path7.join(dirPath, "actions.yaml");
|
|
20864
|
+
if (fs7.existsSync(actionsPath)) {
|
|
19752
20865
|
try {
|
|
19753
|
-
const actionsRaw =
|
|
20866
|
+
const actionsRaw = fs7.readFileSync(actionsPath, "utf-8");
|
|
19754
20867
|
actions = YAML3.parse(actionsRaw);
|
|
19755
20868
|
} catch {
|
|
19756
20869
|
}
|
|
@@ -19777,10 +20890,10 @@ function loadAllSkills(marvinDir) {
|
|
|
19777
20890
|
}
|
|
19778
20891
|
try {
|
|
19779
20892
|
const builtinDir = getBuiltinSkillsDir();
|
|
19780
|
-
if (
|
|
19781
|
-
for (const entry of
|
|
19782
|
-
const entryPath =
|
|
19783
|
-
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;
|
|
19784
20897
|
if (skills.has(entry)) continue;
|
|
19785
20898
|
const skill = loadSkillFromDirectory(entryPath);
|
|
19786
20899
|
if (skill) skills.set(skill.id, skill);
|
|
@@ -19789,18 +20902,18 @@ function loadAllSkills(marvinDir) {
|
|
|
19789
20902
|
} catch {
|
|
19790
20903
|
}
|
|
19791
20904
|
if (marvinDir) {
|
|
19792
|
-
const skillsDir =
|
|
19793
|
-
if (
|
|
20905
|
+
const skillsDir = path7.join(marvinDir, "skills");
|
|
20906
|
+
if (fs7.existsSync(skillsDir)) {
|
|
19794
20907
|
let entries;
|
|
19795
20908
|
try {
|
|
19796
|
-
entries =
|
|
20909
|
+
entries = fs7.readdirSync(skillsDir);
|
|
19797
20910
|
} catch {
|
|
19798
20911
|
entries = [];
|
|
19799
20912
|
}
|
|
19800
20913
|
for (const entry of entries) {
|
|
19801
|
-
const entryPath =
|
|
20914
|
+
const entryPath = path7.join(skillsDir, entry);
|
|
19802
20915
|
try {
|
|
19803
|
-
if (
|
|
20916
|
+
if (fs7.statSync(entryPath).isDirectory()) {
|
|
19804
20917
|
const skill = loadSkillFromDirectory(entryPath);
|
|
19805
20918
|
if (skill) skills.set(skill.id, skill);
|
|
19806
20919
|
continue;
|
|
@@ -19810,7 +20923,7 @@ function loadAllSkills(marvinDir) {
|
|
|
19810
20923
|
}
|
|
19811
20924
|
if (!entry.endsWith(".yaml") && !entry.endsWith(".yml")) continue;
|
|
19812
20925
|
try {
|
|
19813
|
-
const raw =
|
|
20926
|
+
const raw = fs7.readFileSync(entryPath, "utf-8");
|
|
19814
20927
|
const parsed = YAML3.parse(raw);
|
|
19815
20928
|
if (!parsed?.id || !parsed?.name || !parsed?.version) continue;
|
|
19816
20929
|
const skill = {
|
|
@@ -19915,12 +21028,12 @@ function getSkillAgentDefinitions(skillIds, allSkills) {
|
|
|
19915
21028
|
return agents;
|
|
19916
21029
|
}
|
|
19917
21030
|
function migrateYamlToSkillMd(yamlPath, outputDir) {
|
|
19918
|
-
const raw =
|
|
21031
|
+
const raw = fs7.readFileSync(yamlPath, "utf-8");
|
|
19919
21032
|
const parsed = YAML3.parse(raw);
|
|
19920
21033
|
if (!parsed?.id || !parsed?.name) {
|
|
19921
21034
|
throw new Error(`Invalid skill YAML: missing required fields (id, name)`);
|
|
19922
21035
|
}
|
|
19923
|
-
|
|
21036
|
+
fs7.mkdirSync(outputDir, { recursive: true });
|
|
19924
21037
|
const frontmatter = {
|
|
19925
21038
|
name: parsed.id,
|
|
19926
21039
|
description: parsed.description ?? ""
|
|
@@ -19934,15 +21047,15 @@ function migrateYamlToSkillMd(yamlPath, outputDir) {
|
|
|
19934
21047
|
const skillMd = matter2.stringify(wildcardPrompt ? `
|
|
19935
21048
|
${wildcardPrompt}
|
|
19936
21049
|
` : "\n", frontmatter);
|
|
19937
|
-
|
|
21050
|
+
fs7.writeFileSync(path7.join(outputDir, "SKILL.md"), skillMd, "utf-8");
|
|
19938
21051
|
if (promptFragments) {
|
|
19939
21052
|
const personaKeys = Object.keys(promptFragments).filter((k) => k !== "*");
|
|
19940
21053
|
if (personaKeys.length > 0) {
|
|
19941
|
-
const personasDir =
|
|
19942
|
-
|
|
21054
|
+
const personasDir = path7.join(outputDir, "personas");
|
|
21055
|
+
fs7.mkdirSync(personasDir, { recursive: true });
|
|
19943
21056
|
for (const personaId of personaKeys) {
|
|
19944
|
-
|
|
19945
|
-
|
|
21057
|
+
fs7.writeFileSync(
|
|
21058
|
+
path7.join(personasDir, `${personaId}.md`),
|
|
19946
21059
|
`${promptFragments[personaId]}
|
|
19947
21060
|
`,
|
|
19948
21061
|
"utf-8"
|
|
@@ -19952,8 +21065,8 @@ ${wildcardPrompt}
|
|
|
19952
21065
|
}
|
|
19953
21066
|
const actions = parsed.actions;
|
|
19954
21067
|
if (actions && actions.length > 0) {
|
|
19955
|
-
|
|
19956
|
-
|
|
21068
|
+
fs7.writeFileSync(
|
|
21069
|
+
path7.join(outputDir, "actions.yaml"),
|
|
19957
21070
|
YAML3.stringify(actions),
|
|
19958
21071
|
"utf-8"
|
|
19959
21072
|
);
|
|
@@ -20032,7 +21145,7 @@ function openBrowser(url2) {
|
|
|
20032
21145
|
var runningServer = null;
|
|
20033
21146
|
function createWebTools(store, projectName, navGroups) {
|
|
20034
21147
|
return [
|
|
20035
|
-
|
|
21148
|
+
tool22(
|
|
20036
21149
|
"start_web_dashboard",
|
|
20037
21150
|
"Start the Marvin web dashboard on a local port. Returns the base URL. If already running, returns the existing URL.",
|
|
20038
21151
|
{
|
|
@@ -20064,7 +21177,7 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
20064
21177
|
};
|
|
20065
21178
|
}
|
|
20066
21179
|
),
|
|
20067
|
-
|
|
21180
|
+
tool22(
|
|
20068
21181
|
"stop_web_dashboard",
|
|
20069
21182
|
"Stop the running Marvin web dashboard.",
|
|
20070
21183
|
{},
|
|
@@ -20084,7 +21197,7 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
20084
21197
|
};
|
|
20085
21198
|
}
|
|
20086
21199
|
),
|
|
20087
|
-
|
|
21200
|
+
tool22(
|
|
20088
21201
|
"get_web_dashboard_urls",
|
|
20089
21202
|
"Get all available dashboard page URLs. The dashboard must be running.",
|
|
20090
21203
|
{},
|
|
@@ -20110,7 +21223,7 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
20110
21223
|
},
|
|
20111
21224
|
{ annotations: { readOnlyHint: true } }
|
|
20112
21225
|
),
|
|
20113
|
-
|
|
21226
|
+
tool22(
|
|
20114
21227
|
"get_dashboard_overview",
|
|
20115
21228
|
"Get the project overview data: document type counts and recent activity. Works without the web server running.",
|
|
20116
21229
|
{},
|
|
@@ -20132,7 +21245,7 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
20132
21245
|
},
|
|
20133
21246
|
{ annotations: { readOnlyHint: true } }
|
|
20134
21247
|
),
|
|
20135
|
-
|
|
21248
|
+
tool22(
|
|
20136
21249
|
"get_dashboard_gar",
|
|
20137
21250
|
"Get the GAR (Governance, Actions, Risks) report as JSON. Works without the web server running.",
|
|
20138
21251
|
{},
|
|
@@ -20144,7 +21257,7 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
20144
21257
|
},
|
|
20145
21258
|
{ annotations: { readOnlyHint: true } }
|
|
20146
21259
|
),
|
|
20147
|
-
|
|
21260
|
+
tool22(
|
|
20148
21261
|
"get_dashboard_board",
|
|
20149
21262
|
"Get board data showing documents grouped by status. Optionally filter by document type. Works without the web server running.",
|
|
20150
21263
|
{
|
|
@@ -20196,8 +21309,8 @@ function createMarvinMcpServer(store, options) {
|
|
|
20196
21309
|
}
|
|
20197
21310
|
|
|
20198
21311
|
// src/agent/session.ts
|
|
20199
|
-
import * as
|
|
20200
|
-
import * as
|
|
21312
|
+
import * as fs10 from "fs";
|
|
21313
|
+
import * as path10 from "path";
|
|
20201
21314
|
import * as readline from "readline";
|
|
20202
21315
|
import chalk from "chalk";
|
|
20203
21316
|
import ora from "ora";
|
|
@@ -20206,13 +21319,13 @@ import {
|
|
|
20206
21319
|
} from "@anthropic-ai/claude-agent-sdk";
|
|
20207
21320
|
|
|
20208
21321
|
// src/storage/session-store.ts
|
|
20209
|
-
import * as
|
|
20210
|
-
import * as
|
|
21322
|
+
import * as fs8 from "fs";
|
|
21323
|
+
import * as path8 from "path";
|
|
20211
21324
|
import * as YAML4 from "yaml";
|
|
20212
21325
|
var SessionStore = class {
|
|
20213
21326
|
filePath;
|
|
20214
21327
|
constructor(marvinDir) {
|
|
20215
|
-
this.filePath =
|
|
21328
|
+
this.filePath = path8.join(marvinDir, "sessions.yaml");
|
|
20216
21329
|
}
|
|
20217
21330
|
list() {
|
|
20218
21331
|
const entries = this.load();
|
|
@@ -20253,9 +21366,9 @@ var SessionStore = class {
|
|
|
20253
21366
|
this.write(entries);
|
|
20254
21367
|
}
|
|
20255
21368
|
load() {
|
|
20256
|
-
if (!
|
|
21369
|
+
if (!fs8.existsSync(this.filePath)) return [];
|
|
20257
21370
|
try {
|
|
20258
|
-
const raw =
|
|
21371
|
+
const raw = fs8.readFileSync(this.filePath, "utf-8");
|
|
20259
21372
|
const parsed = YAML4.parse(raw);
|
|
20260
21373
|
if (!Array.isArray(parsed)) return [];
|
|
20261
21374
|
return parsed;
|
|
@@ -20264,11 +21377,11 @@ var SessionStore = class {
|
|
|
20264
21377
|
}
|
|
20265
21378
|
}
|
|
20266
21379
|
write(entries) {
|
|
20267
|
-
const dir =
|
|
20268
|
-
if (!
|
|
20269
|
-
|
|
21380
|
+
const dir = path8.dirname(this.filePath);
|
|
21381
|
+
if (!fs8.existsSync(dir)) {
|
|
21382
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
20270
21383
|
}
|
|
20271
|
-
|
|
21384
|
+
fs8.writeFileSync(this.filePath, YAML4.stringify(entries), "utf-8");
|
|
20272
21385
|
}
|
|
20273
21386
|
};
|
|
20274
21387
|
|
|
@@ -20304,8 +21417,8 @@ function slugify3(text) {
|
|
|
20304
21417
|
}
|
|
20305
21418
|
|
|
20306
21419
|
// src/sources/manifest.ts
|
|
20307
|
-
import * as
|
|
20308
|
-
import * as
|
|
21420
|
+
import * as fs9 from "fs";
|
|
21421
|
+
import * as path9 from "path";
|
|
20309
21422
|
import * as crypto from "crypto";
|
|
20310
21423
|
import * as YAML5 from "yaml";
|
|
20311
21424
|
var MANIFEST_FILE = ".manifest.yaml";
|
|
@@ -20318,37 +21431,37 @@ var SourceManifestManager = class {
|
|
|
20318
21431
|
manifestPath;
|
|
20319
21432
|
sourcesDir;
|
|
20320
21433
|
constructor(marvinDir) {
|
|
20321
|
-
this.sourcesDir =
|
|
20322
|
-
this.manifestPath =
|
|
21434
|
+
this.sourcesDir = path9.join(marvinDir, "sources");
|
|
21435
|
+
this.manifestPath = path9.join(this.sourcesDir, MANIFEST_FILE);
|
|
20323
21436
|
this.manifest = this.load();
|
|
20324
21437
|
}
|
|
20325
21438
|
load() {
|
|
20326
|
-
if (!
|
|
21439
|
+
if (!fs9.existsSync(this.manifestPath)) {
|
|
20327
21440
|
return emptyManifest();
|
|
20328
21441
|
}
|
|
20329
|
-
const raw =
|
|
21442
|
+
const raw = fs9.readFileSync(this.manifestPath, "utf-8");
|
|
20330
21443
|
const parsed = YAML5.parse(raw);
|
|
20331
21444
|
return parsed ?? emptyManifest();
|
|
20332
21445
|
}
|
|
20333
21446
|
save() {
|
|
20334
|
-
|
|
20335
|
-
|
|
21447
|
+
fs9.mkdirSync(this.sourcesDir, { recursive: true });
|
|
21448
|
+
fs9.writeFileSync(this.manifestPath, YAML5.stringify(this.manifest), "utf-8");
|
|
20336
21449
|
}
|
|
20337
21450
|
scan() {
|
|
20338
21451
|
const added = [];
|
|
20339
21452
|
const changed = [];
|
|
20340
21453
|
const removed = [];
|
|
20341
|
-
if (!
|
|
21454
|
+
if (!fs9.existsSync(this.sourcesDir)) {
|
|
20342
21455
|
return { added, changed, removed };
|
|
20343
21456
|
}
|
|
20344
21457
|
const onDisk = new Set(
|
|
20345
|
-
|
|
20346
|
-
const ext =
|
|
21458
|
+
fs9.readdirSync(this.sourcesDir).filter((f) => {
|
|
21459
|
+
const ext = path9.extname(f).toLowerCase();
|
|
20347
21460
|
return SOURCE_EXTENSIONS.includes(ext);
|
|
20348
21461
|
})
|
|
20349
21462
|
);
|
|
20350
21463
|
for (const fileName of onDisk) {
|
|
20351
|
-
const filePath =
|
|
21464
|
+
const filePath = path9.join(this.sourcesDir, fileName);
|
|
20352
21465
|
const hash2 = this.hashFile(filePath);
|
|
20353
21466
|
const existing = this.manifest.files[fileName];
|
|
20354
21467
|
if (!existing) {
|
|
@@ -20411,7 +21524,7 @@ var SourceManifestManager = class {
|
|
|
20411
21524
|
this.save();
|
|
20412
21525
|
}
|
|
20413
21526
|
hashFile(filePath) {
|
|
20414
|
-
const content =
|
|
21527
|
+
const content = fs9.readFileSync(filePath);
|
|
20415
21528
|
return crypto.createHash("sha256").update(content).digest("hex");
|
|
20416
21529
|
}
|
|
20417
21530
|
};
|
|
@@ -20426,8 +21539,8 @@ async function startSession(options) {
|
|
|
20426
21539
|
const skillRegistrations = collectSkillRegistrations(skillIds, allSkills);
|
|
20427
21540
|
const store = new DocumentStore(marvinDir, [...pluginRegistrations, ...skillRegistrations]);
|
|
20428
21541
|
const sessionStore = new SessionStore(marvinDir);
|
|
20429
|
-
const sourcesDir =
|
|
20430
|
-
const hasSourcesDir =
|
|
21542
|
+
const sourcesDir = path10.join(marvinDir, "sources");
|
|
21543
|
+
const hasSourcesDir = fs10.existsSync(sourcesDir);
|
|
20431
21544
|
const manifest = hasSourcesDir ? new SourceManifestManager(marvinDir) : void 0;
|
|
20432
21545
|
const pluginTools = plugin ? getPluginTools(plugin, store, marvinDir) : [];
|
|
20433
21546
|
const pluginPromptFragment = plugin ? getPluginPromptFragment(plugin, persona.id) : void 0;
|
|
@@ -20636,13 +21749,13 @@ Session ended with error: ${message.subtype}`));
|
|
|
20636
21749
|
}
|
|
20637
21750
|
|
|
20638
21751
|
// src/mcp/stdio-server.ts
|
|
20639
|
-
import * as
|
|
20640
|
-
import * as
|
|
21752
|
+
import * as fs11 from "fs";
|
|
21753
|
+
import * as path11 from "path";
|
|
20641
21754
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
20642
21755
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
20643
21756
|
|
|
20644
21757
|
// src/skills/action-tools.ts
|
|
20645
|
-
import { tool as
|
|
21758
|
+
import { tool as tool23 } from "@anthropic-ai/claude-agent-sdk";
|
|
20646
21759
|
|
|
20647
21760
|
// src/skills/action-runner.ts
|
|
20648
21761
|
import { query as query3 } from "@anthropic-ai/claude-agent-sdk";
|
|
@@ -20708,7 +21821,7 @@ function createSkillActionTools(skills, context) {
|
|
|
20708
21821
|
if (!skill.actions) continue;
|
|
20709
21822
|
for (const action of skill.actions) {
|
|
20710
21823
|
tools.push(
|
|
20711
|
-
|
|
21824
|
+
tool23(
|
|
20712
21825
|
`${skill.id}__${action.id}`,
|
|
20713
21826
|
action.description,
|
|
20714
21827
|
{
|
|
@@ -20800,10 +21913,10 @@ ${lines.join("\n\n")}`;
|
|
|
20800
21913
|
}
|
|
20801
21914
|
|
|
20802
21915
|
// src/mcp/persona-tools.ts
|
|
20803
|
-
import { tool as
|
|
21916
|
+
import { tool as tool24 } from "@anthropic-ai/claude-agent-sdk";
|
|
20804
21917
|
function createPersonaTools(ctx, marvinDir) {
|
|
20805
21918
|
return [
|
|
20806
|
-
|
|
21919
|
+
tool24(
|
|
20807
21920
|
"set_persona",
|
|
20808
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.",
|
|
20809
21922
|
{
|
|
@@ -20833,7 +21946,7 @@ ${summaries}`
|
|
|
20833
21946
|
};
|
|
20834
21947
|
}
|
|
20835
21948
|
),
|
|
20836
|
-
|
|
21949
|
+
tool24(
|
|
20837
21950
|
"get_persona_guidance",
|
|
20838
21951
|
"Get guidance for a persona without changing the active persona. If no persona is specified, lists all available personas with summaries.",
|
|
20839
21952
|
{
|
|
@@ -20935,8 +22048,8 @@ function collectTools(marvinDir) {
|
|
|
20935
22048
|
const plugin = resolvePlugin(config2.methodology);
|
|
20936
22049
|
const registrations = plugin?.documentTypeRegistrations ?? [];
|
|
20937
22050
|
const store = new DocumentStore(marvinDir, registrations);
|
|
20938
|
-
const sourcesDir =
|
|
20939
|
-
const hasSourcesDir =
|
|
22051
|
+
const sourcesDir = path11.join(marvinDir, "sources");
|
|
22052
|
+
const hasSourcesDir = fs11.existsSync(sourcesDir);
|
|
20940
22053
|
const manifest = hasSourcesDir ? new SourceManifestManager(marvinDir) : void 0;
|
|
20941
22054
|
const pluginTools = plugin ? getPluginTools(plugin, store, marvinDir) : [];
|
|
20942
22055
|
const sessionStore = new SessionStore(marvinDir);
|
|
@@ -20944,7 +22057,7 @@ function collectTools(marvinDir) {
|
|
|
20944
22057
|
const allSkillIds = [...allSkills.keys()];
|
|
20945
22058
|
const codeSkillTools = getSkillTools(allSkillIds, allSkills, store);
|
|
20946
22059
|
const skillsWithActions = allSkillIds.map((id) => allSkills.get(id)).filter((s) => s.actions && s.actions.length > 0);
|
|
20947
|
-
const projectRoot =
|
|
22060
|
+
const projectRoot = path11.dirname(marvinDir);
|
|
20948
22061
|
const actionTools = createSkillActionTools(skillsWithActions, { store, marvinDir, projectRoot });
|
|
20949
22062
|
const allSkillRegs = collectSkillRegistrations(allSkillIds, allSkills);
|
|
20950
22063
|
const navGroups = buildNavGroups({
|
|
@@ -21011,8 +22124,8 @@ async function startStdioServer(options) {
|
|
|
21011
22124
|
import { Command } from "commander";
|
|
21012
22125
|
|
|
21013
22126
|
// src/cli/commands/init.ts
|
|
21014
|
-
import * as
|
|
21015
|
-
import * as
|
|
22127
|
+
import * as fs12 from "fs";
|
|
22128
|
+
import * as path12 from "path";
|
|
21016
22129
|
import * as YAML6 from "yaml";
|
|
21017
22130
|
import chalk2 from "chalk";
|
|
21018
22131
|
import { input, confirm, select } from "@inquirer/prompts";
|
|
@@ -21078,7 +22191,7 @@ async function initCommand() {
|
|
|
21078
22191
|
}
|
|
21079
22192
|
const projectName = await input({
|
|
21080
22193
|
message: "Project name:",
|
|
21081
|
-
default:
|
|
22194
|
+
default: path12.basename(cwd)
|
|
21082
22195
|
});
|
|
21083
22196
|
const methodology = await select({
|
|
21084
22197
|
message: "Methodology:",
|
|
@@ -21090,21 +22203,21 @@ async function initCommand() {
|
|
|
21090
22203
|
});
|
|
21091
22204
|
const plugin = resolvePlugin(methodology);
|
|
21092
22205
|
const registrations = plugin?.documentTypeRegistrations ?? [];
|
|
21093
|
-
const marvinDir =
|
|
22206
|
+
const marvinDir = path12.join(cwd, ".marvin");
|
|
21094
22207
|
const dirs = [
|
|
21095
22208
|
marvinDir,
|
|
21096
|
-
|
|
21097
|
-
|
|
21098
|
-
|
|
21099
|
-
|
|
21100
|
-
|
|
21101
|
-
|
|
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")
|
|
21102
22215
|
];
|
|
21103
22216
|
for (const reg of registrations) {
|
|
21104
|
-
dirs.push(
|
|
22217
|
+
dirs.push(path12.join(marvinDir, "docs", reg.dirName));
|
|
21105
22218
|
}
|
|
21106
22219
|
for (const dir of dirs) {
|
|
21107
|
-
|
|
22220
|
+
fs12.mkdirSync(dir, { recursive: true });
|
|
21108
22221
|
}
|
|
21109
22222
|
const config2 = {
|
|
21110
22223
|
name: projectName,
|
|
@@ -21118,13 +22231,13 @@ async function initCommand() {
|
|
|
21118
22231
|
if (methodology === "sap-aem") {
|
|
21119
22232
|
config2.aem = { currentPhase: "assess-use-case" };
|
|
21120
22233
|
}
|
|
21121
|
-
|
|
21122
|
-
|
|
22234
|
+
fs12.writeFileSync(
|
|
22235
|
+
path12.join(marvinDir, "config.yaml"),
|
|
21123
22236
|
YAML6.stringify(config2),
|
|
21124
22237
|
"utf-8"
|
|
21125
22238
|
);
|
|
21126
|
-
|
|
21127
|
-
|
|
22239
|
+
fs12.writeFileSync(
|
|
22240
|
+
path12.join(marvinDir, "CLAUDE.md"),
|
|
21128
22241
|
getDefaultClaudeMdContent(projectName),
|
|
21129
22242
|
"utf-8"
|
|
21130
22243
|
);
|
|
@@ -21151,18 +22264,18 @@ Initialized Marvin project "${projectName}" in ${cwd}`));
|
|
|
21151
22264
|
const sourceDir = await input({
|
|
21152
22265
|
message: "Path to directory containing source documents:"
|
|
21153
22266
|
});
|
|
21154
|
-
const resolvedDir =
|
|
21155
|
-
if (
|
|
22267
|
+
const resolvedDir = path12.resolve(sourceDir);
|
|
22268
|
+
if (fs12.existsSync(resolvedDir) && fs12.statSync(resolvedDir).isDirectory()) {
|
|
21156
22269
|
const sourceExts = [".pdf", ".md", ".txt"];
|
|
21157
|
-
const files =
|
|
21158
|
-
const ext =
|
|
22270
|
+
const files = fs12.readdirSync(resolvedDir).filter((f) => {
|
|
22271
|
+
const ext = path12.extname(f).toLowerCase();
|
|
21159
22272
|
return sourceExts.includes(ext);
|
|
21160
22273
|
});
|
|
21161
22274
|
let copied = 0;
|
|
21162
22275
|
for (const file2 of files) {
|
|
21163
|
-
const src =
|
|
21164
|
-
const dest =
|
|
21165
|
-
|
|
22276
|
+
const src = path12.join(resolvedDir, file2);
|
|
22277
|
+
const dest = path12.join(marvinDir, "sources", file2);
|
|
22278
|
+
fs12.copyFileSync(src, dest);
|
|
21166
22279
|
copied++;
|
|
21167
22280
|
}
|
|
21168
22281
|
if (copied > 0) {
|
|
@@ -21452,13 +22565,13 @@ async function setApiKey() {
|
|
|
21452
22565
|
}
|
|
21453
22566
|
|
|
21454
22567
|
// src/cli/commands/ingest.ts
|
|
21455
|
-
import * as
|
|
21456
|
-
import * as
|
|
22568
|
+
import * as fs14 from "fs";
|
|
22569
|
+
import * as path14 from "path";
|
|
21457
22570
|
import chalk8 from "chalk";
|
|
21458
22571
|
|
|
21459
22572
|
// src/sources/ingest.ts
|
|
21460
|
-
import * as
|
|
21461
|
-
import * as
|
|
22573
|
+
import * as fs13 from "fs";
|
|
22574
|
+
import * as path13 from "path";
|
|
21462
22575
|
import chalk7 from "chalk";
|
|
21463
22576
|
import ora2 from "ora";
|
|
21464
22577
|
import { query as query4 } from "@anthropic-ai/claude-agent-sdk";
|
|
@@ -21561,15 +22674,15 @@ async function ingestFile(options) {
|
|
|
21561
22674
|
const persona = getPersona(personaId);
|
|
21562
22675
|
const manifest = new SourceManifestManager(marvinDir);
|
|
21563
22676
|
const sourcesDir = manifest.sourcesDir;
|
|
21564
|
-
const filePath =
|
|
21565
|
-
if (!
|
|
22677
|
+
const filePath = path13.join(sourcesDir, fileName);
|
|
22678
|
+
if (!fs13.existsSync(filePath)) {
|
|
21566
22679
|
throw new Error(`Source file not found: ${filePath}`);
|
|
21567
22680
|
}
|
|
21568
|
-
const ext =
|
|
22681
|
+
const ext = path13.extname(fileName).toLowerCase();
|
|
21569
22682
|
const isPdf = ext === ".pdf";
|
|
21570
22683
|
let fileContent = null;
|
|
21571
22684
|
if (!isPdf) {
|
|
21572
|
-
fileContent =
|
|
22685
|
+
fileContent = fs13.readFileSync(filePath, "utf-8");
|
|
21573
22686
|
}
|
|
21574
22687
|
const store = new DocumentStore(marvinDir);
|
|
21575
22688
|
const createdArtifacts = [];
|
|
@@ -21672,9 +22785,9 @@ Ingest ended with error: ${message.subtype}`)
|
|
|
21672
22785
|
async function ingestCommand(file2, options) {
|
|
21673
22786
|
const project = loadProject();
|
|
21674
22787
|
const marvinDir = project.marvinDir;
|
|
21675
|
-
const sourcesDir =
|
|
21676
|
-
if (!
|
|
21677
|
-
|
|
22788
|
+
const sourcesDir = path14.join(marvinDir, "sources");
|
|
22789
|
+
if (!fs14.existsSync(sourcesDir)) {
|
|
22790
|
+
fs14.mkdirSync(sourcesDir, { recursive: true });
|
|
21678
22791
|
}
|
|
21679
22792
|
const manifest = new SourceManifestManager(marvinDir);
|
|
21680
22793
|
manifest.scan();
|
|
@@ -21685,8 +22798,8 @@ async function ingestCommand(file2, options) {
|
|
|
21685
22798
|
return;
|
|
21686
22799
|
}
|
|
21687
22800
|
if (file2) {
|
|
21688
|
-
const filePath =
|
|
21689
|
-
if (!
|
|
22801
|
+
const filePath = path14.join(sourcesDir, file2);
|
|
22802
|
+
if (!fs14.existsSync(filePath)) {
|
|
21690
22803
|
console.log(chalk8.red(`Source file not found: ${file2}`));
|
|
21691
22804
|
console.log(chalk8.dim(`Expected at: ${filePath}`));
|
|
21692
22805
|
console.log(chalk8.dim(`Drop files into .marvin/sources/ and try again.`));
|
|
@@ -21753,7 +22866,7 @@ import ora3 from "ora";
|
|
|
21753
22866
|
import { input as input3 } from "@inquirer/prompts";
|
|
21754
22867
|
|
|
21755
22868
|
// src/git/repository.ts
|
|
21756
|
-
import * as
|
|
22869
|
+
import * as path15 from "path";
|
|
21757
22870
|
import simpleGit from "simple-git";
|
|
21758
22871
|
var MARVIN_GITIGNORE = `node_modules/
|
|
21759
22872
|
.DS_Store
|
|
@@ -21773,7 +22886,7 @@ var DIR_TYPE_LABELS = {
|
|
|
21773
22886
|
function buildCommitMessage(files) {
|
|
21774
22887
|
const counts = /* @__PURE__ */ new Map();
|
|
21775
22888
|
for (const f of files) {
|
|
21776
|
-
const parts2 = f.split(
|
|
22889
|
+
const parts2 = f.split(path15.sep).join("/").split("/");
|
|
21777
22890
|
const docsIdx = parts2.indexOf("docs");
|
|
21778
22891
|
if (docsIdx !== -1 && docsIdx + 1 < parts2.length) {
|
|
21779
22892
|
const dirName = parts2[docsIdx + 1];
|
|
@@ -21813,9 +22926,9 @@ var MarvinGit = class {
|
|
|
21813
22926
|
);
|
|
21814
22927
|
}
|
|
21815
22928
|
await this.git.init();
|
|
21816
|
-
const { writeFileSync:
|
|
21817
|
-
|
|
21818
|
-
|
|
22929
|
+
const { writeFileSync: writeFileSync11 } = await import("fs");
|
|
22930
|
+
writeFileSync11(
|
|
22931
|
+
path15.join(this.marvinDir, ".gitignore"),
|
|
21819
22932
|
MARVIN_GITIGNORE,
|
|
21820
22933
|
"utf-8"
|
|
21821
22934
|
);
|
|
@@ -21935,7 +23048,7 @@ var MarvinGit = class {
|
|
|
21935
23048
|
}
|
|
21936
23049
|
}
|
|
21937
23050
|
static async clone(url2, targetDir) {
|
|
21938
|
-
const marvinDir =
|
|
23051
|
+
const marvinDir = path15.join(targetDir, ".marvin");
|
|
21939
23052
|
const { existsSync: existsSync17 } = await import("fs");
|
|
21940
23053
|
if (existsSync17(marvinDir)) {
|
|
21941
23054
|
throw new GitSyncError(
|
|
@@ -22121,8 +23234,8 @@ async function serveCommand() {
|
|
|
22121
23234
|
}
|
|
22122
23235
|
|
|
22123
23236
|
// src/cli/commands/skills.ts
|
|
22124
|
-
import * as
|
|
22125
|
-
import * as
|
|
23237
|
+
import * as fs15 from "fs";
|
|
23238
|
+
import * as path16 from "path";
|
|
22126
23239
|
import * as YAML7 from "yaml";
|
|
22127
23240
|
import matter3 from "gray-matter";
|
|
22128
23241
|
import chalk10 from "chalk";
|
|
@@ -22228,14 +23341,14 @@ async function skillsRemoveCommand(skillId, options) {
|
|
|
22228
23341
|
}
|
|
22229
23342
|
async function skillsCreateCommand(name) {
|
|
22230
23343
|
const project = loadProject();
|
|
22231
|
-
const skillsDir =
|
|
22232
|
-
|
|
22233
|
-
const skillDir =
|
|
22234
|
-
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)) {
|
|
22235
23348
|
console.log(chalk10.yellow(`Skill directory already exists: ${skillDir}`));
|
|
22236
23349
|
return;
|
|
22237
23350
|
}
|
|
22238
|
-
|
|
23351
|
+
fs15.mkdirSync(skillDir, { recursive: true });
|
|
22239
23352
|
const displayName = name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
22240
23353
|
const frontmatter = {
|
|
22241
23354
|
name,
|
|
@@ -22249,7 +23362,7 @@ async function skillsCreateCommand(name) {
|
|
|
22249
23362
|
You have the **${displayName}** skill.
|
|
22250
23363
|
`;
|
|
22251
23364
|
const skillMd = matter3.stringify(body, frontmatter);
|
|
22252
|
-
|
|
23365
|
+
fs15.writeFileSync(path16.join(skillDir, "SKILL.md"), skillMd, "utf-8");
|
|
22253
23366
|
const actions = [
|
|
22254
23367
|
{
|
|
22255
23368
|
id: "run",
|
|
@@ -22259,7 +23372,7 @@ You have the **${displayName}** skill.
|
|
|
22259
23372
|
maxTurns: 5
|
|
22260
23373
|
}
|
|
22261
23374
|
];
|
|
22262
|
-
|
|
23375
|
+
fs15.writeFileSync(path16.join(skillDir, "actions.yaml"), YAML7.stringify(actions), "utf-8");
|
|
22263
23376
|
console.log(chalk10.green(`Created skill: ${skillDir}/`));
|
|
22264
23377
|
console.log(chalk10.dim(" SKILL.md \u2014 skill definition and prompt"));
|
|
22265
23378
|
console.log(chalk10.dim(" actions.yaml \u2014 action definitions"));
|
|
@@ -22267,14 +23380,14 @@ You have the **${displayName}** skill.
|
|
|
22267
23380
|
}
|
|
22268
23381
|
async function skillsMigrateCommand() {
|
|
22269
23382
|
const project = loadProject();
|
|
22270
|
-
const skillsDir =
|
|
22271
|
-
if (!
|
|
23383
|
+
const skillsDir = path16.join(project.marvinDir, "skills");
|
|
23384
|
+
if (!fs15.existsSync(skillsDir)) {
|
|
22272
23385
|
console.log(chalk10.dim("No skills directory found."));
|
|
22273
23386
|
return;
|
|
22274
23387
|
}
|
|
22275
23388
|
let entries;
|
|
22276
23389
|
try {
|
|
22277
|
-
entries =
|
|
23390
|
+
entries = fs15.readdirSync(skillsDir);
|
|
22278
23391
|
} catch {
|
|
22279
23392
|
console.log(chalk10.red("Could not read skills directory."));
|
|
22280
23393
|
return;
|
|
@@ -22286,16 +23399,16 @@ async function skillsMigrateCommand() {
|
|
|
22286
23399
|
}
|
|
22287
23400
|
let migrated = 0;
|
|
22288
23401
|
for (const file2 of yamlFiles) {
|
|
22289
|
-
const yamlPath =
|
|
23402
|
+
const yamlPath = path16.join(skillsDir, file2);
|
|
22290
23403
|
const baseName = file2.replace(/\.(yaml|yml)$/, "");
|
|
22291
|
-
const outputDir =
|
|
22292
|
-
if (
|
|
23404
|
+
const outputDir = path16.join(skillsDir, baseName);
|
|
23405
|
+
if (fs15.existsSync(outputDir)) {
|
|
22293
23406
|
console.log(chalk10.yellow(`Skipping "${file2}" \u2014 directory "${baseName}/" already exists.`));
|
|
22294
23407
|
continue;
|
|
22295
23408
|
}
|
|
22296
23409
|
try {
|
|
22297
23410
|
migrateYamlToSkillMd(yamlPath, outputDir);
|
|
22298
|
-
|
|
23411
|
+
fs15.renameSync(yamlPath, `${yamlPath}.bak`);
|
|
22299
23412
|
console.log(chalk10.green(`Migrated "${file2}" \u2192 "${baseName}/"`));
|
|
22300
23413
|
migrated++;
|
|
22301
23414
|
} catch (err) {
|
|
@@ -22309,35 +23422,35 @@ ${migrated} skill(s) migrated. Original files renamed to *.bak`));
|
|
|
22309
23422
|
}
|
|
22310
23423
|
|
|
22311
23424
|
// src/cli/commands/import.ts
|
|
22312
|
-
import * as
|
|
22313
|
-
import * as
|
|
23425
|
+
import * as fs18 from "fs";
|
|
23426
|
+
import * as path19 from "path";
|
|
22314
23427
|
import chalk11 from "chalk";
|
|
22315
23428
|
|
|
22316
23429
|
// src/import/engine.ts
|
|
22317
|
-
import * as
|
|
22318
|
-
import * as
|
|
23430
|
+
import * as fs17 from "fs";
|
|
23431
|
+
import * as path18 from "path";
|
|
22319
23432
|
import matter5 from "gray-matter";
|
|
22320
23433
|
|
|
22321
23434
|
// src/import/classifier.ts
|
|
22322
|
-
import * as
|
|
22323
|
-
import * as
|
|
23435
|
+
import * as fs16 from "fs";
|
|
23436
|
+
import * as path17 from "path";
|
|
22324
23437
|
import matter4 from "gray-matter";
|
|
22325
23438
|
var RAW_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".pdf", ".txt"]);
|
|
22326
23439
|
var CORE_DIR_NAMES = /* @__PURE__ */ new Set(["decisions", "actions", "questions"]);
|
|
22327
23440
|
var ID_PATTERN = /^[A-Z]+-\d{3,}$/;
|
|
22328
23441
|
function classifyPath(inputPath, knownTypes, knownDirNames) {
|
|
22329
|
-
const resolved =
|
|
22330
|
-
const stat =
|
|
23442
|
+
const resolved = path17.resolve(inputPath);
|
|
23443
|
+
const stat = fs16.statSync(resolved);
|
|
22331
23444
|
if (!stat.isDirectory()) {
|
|
22332
23445
|
return classifyFile(resolved, knownTypes);
|
|
22333
23446
|
}
|
|
22334
|
-
if (
|
|
23447
|
+
if (path17.basename(resolved) === ".marvin" || fs16.existsSync(path17.join(resolved, "config.yaml"))) {
|
|
22335
23448
|
return { type: "marvin-project", inputPath: resolved };
|
|
22336
23449
|
}
|
|
22337
23450
|
const allDirNames = /* @__PURE__ */ new Set([...CORE_DIR_NAMES, ...knownDirNames]);
|
|
22338
|
-
const entries =
|
|
23451
|
+
const entries = fs16.readdirSync(resolved);
|
|
22339
23452
|
const hasDocSubdirs = entries.some(
|
|
22340
|
-
(e) => allDirNames.has(e) &&
|
|
23453
|
+
(e) => allDirNames.has(e) && fs16.statSync(path17.join(resolved, e)).isDirectory()
|
|
22341
23454
|
);
|
|
22342
23455
|
if (hasDocSubdirs) {
|
|
22343
23456
|
return { type: "docs-directory", inputPath: resolved };
|
|
@@ -22346,7 +23459,7 @@ function classifyPath(inputPath, knownTypes, knownDirNames) {
|
|
|
22346
23459
|
if (mdFiles.length > 0) {
|
|
22347
23460
|
const hasMarvinDocs = mdFiles.some((f) => {
|
|
22348
23461
|
try {
|
|
22349
|
-
const raw =
|
|
23462
|
+
const raw = fs16.readFileSync(path17.join(resolved, f), "utf-8");
|
|
22350
23463
|
const { data } = matter4(raw);
|
|
22351
23464
|
return isValidMarvinDocument(data, knownTypes);
|
|
22352
23465
|
} catch {
|
|
@@ -22360,14 +23473,14 @@ function classifyPath(inputPath, knownTypes, knownDirNames) {
|
|
|
22360
23473
|
return { type: "raw-source-dir", inputPath: resolved };
|
|
22361
23474
|
}
|
|
22362
23475
|
function classifyFile(filePath, knownTypes) {
|
|
22363
|
-
const resolved =
|
|
22364
|
-
const ext =
|
|
23476
|
+
const resolved = path17.resolve(filePath);
|
|
23477
|
+
const ext = path17.extname(resolved).toLowerCase();
|
|
22365
23478
|
if (RAW_SOURCE_EXTENSIONS.has(ext)) {
|
|
22366
23479
|
return { type: "raw-source-file", inputPath: resolved };
|
|
22367
23480
|
}
|
|
22368
23481
|
if (ext === ".md") {
|
|
22369
23482
|
try {
|
|
22370
|
-
const raw =
|
|
23483
|
+
const raw = fs16.readFileSync(resolved, "utf-8");
|
|
22371
23484
|
const { data } = matter4(raw);
|
|
22372
23485
|
if (isValidMarvinDocument(data, knownTypes)) {
|
|
22373
23486
|
return { type: "marvin-document", inputPath: resolved };
|
|
@@ -22492,9 +23605,9 @@ function executeImportPlan(plan, store, marvinDir, options) {
|
|
|
22492
23605
|
continue;
|
|
22493
23606
|
}
|
|
22494
23607
|
if (item.action === "copy") {
|
|
22495
|
-
const targetDir =
|
|
22496
|
-
|
|
22497
|
-
|
|
23608
|
+
const targetDir = path18.dirname(item.targetPath);
|
|
23609
|
+
fs17.mkdirSync(targetDir, { recursive: true });
|
|
23610
|
+
fs17.copyFileSync(item.sourcePath, item.targetPath);
|
|
22498
23611
|
copied++;
|
|
22499
23612
|
continue;
|
|
22500
23613
|
}
|
|
@@ -22530,19 +23643,19 @@ function formatPlanSummary(plan) {
|
|
|
22530
23643
|
lines.push(`Documents to import: ${imports.length}`);
|
|
22531
23644
|
for (const item of imports) {
|
|
22532
23645
|
const idInfo = item.originalId !== item.newId ? `${item.originalId} \u2192 ${item.newId}` : item.newId ?? item.originalId ?? "";
|
|
22533
|
-
lines.push(` ${idInfo} ${
|
|
23646
|
+
lines.push(` ${idInfo} ${path18.basename(item.sourcePath)}`);
|
|
22534
23647
|
}
|
|
22535
23648
|
}
|
|
22536
23649
|
if (copies.length > 0) {
|
|
22537
23650
|
lines.push(`Files to copy to sources/: ${copies.length}`);
|
|
22538
23651
|
for (const item of copies) {
|
|
22539
|
-
lines.push(` ${
|
|
23652
|
+
lines.push(` ${path18.basename(item.sourcePath)} \u2192 ${path18.basename(item.targetPath)}`);
|
|
22540
23653
|
}
|
|
22541
23654
|
}
|
|
22542
23655
|
if (skips.length > 0) {
|
|
22543
23656
|
lines.push(`Skipped (conflict): ${skips.length}`);
|
|
22544
23657
|
for (const item of skips) {
|
|
22545
|
-
lines.push(` ${item.originalId ??
|
|
23658
|
+
lines.push(` ${item.originalId ?? path18.basename(item.sourcePath)} ${item.reason ?? ""}`);
|
|
22546
23659
|
}
|
|
22547
23660
|
}
|
|
22548
23661
|
if (plan.items.length === 0) {
|
|
@@ -22575,11 +23688,11 @@ function getDirNameForType(store, type) {
|
|
|
22575
23688
|
}
|
|
22576
23689
|
function collectMarvinDocs(dir, knownTypes) {
|
|
22577
23690
|
const docs = [];
|
|
22578
|
-
const files =
|
|
23691
|
+
const files = fs17.readdirSync(dir).filter((f) => f.endsWith(".md"));
|
|
22579
23692
|
for (const file2 of files) {
|
|
22580
|
-
const filePath =
|
|
23693
|
+
const filePath = path18.join(dir, file2);
|
|
22581
23694
|
try {
|
|
22582
|
-
const raw =
|
|
23695
|
+
const raw = fs17.readFileSync(filePath, "utf-8");
|
|
22583
23696
|
const { data, content } = matter5(raw);
|
|
22584
23697
|
if (isValidMarvinDocument(data, knownTypes)) {
|
|
22585
23698
|
docs.push({
|
|
@@ -22635,23 +23748,23 @@ function planDocImports(docs, store, options) {
|
|
|
22635
23748
|
}
|
|
22636
23749
|
function planFromMarvinProject(classification, store, _marvinDir, options) {
|
|
22637
23750
|
let projectDir = classification.inputPath;
|
|
22638
|
-
if (
|
|
22639
|
-
const inner =
|
|
22640
|
-
if (
|
|
23751
|
+
if (path18.basename(projectDir) !== ".marvin") {
|
|
23752
|
+
const inner = path18.join(projectDir, ".marvin");
|
|
23753
|
+
if (fs17.existsSync(inner)) {
|
|
22641
23754
|
projectDir = inner;
|
|
22642
23755
|
}
|
|
22643
23756
|
}
|
|
22644
|
-
const docsDir =
|
|
22645
|
-
if (!
|
|
23757
|
+
const docsDir = path18.join(projectDir, "docs");
|
|
23758
|
+
if (!fs17.existsSync(docsDir)) {
|
|
22646
23759
|
return [];
|
|
22647
23760
|
}
|
|
22648
23761
|
const knownTypes = store.registeredTypes;
|
|
22649
23762
|
const allDocs = [];
|
|
22650
|
-
const subdirs =
|
|
22651
|
-
(d) =>
|
|
23763
|
+
const subdirs = fs17.readdirSync(docsDir).filter(
|
|
23764
|
+
(d) => fs17.statSync(path18.join(docsDir, d)).isDirectory()
|
|
22652
23765
|
);
|
|
22653
23766
|
for (const subdir of subdirs) {
|
|
22654
|
-
const docs = collectMarvinDocs(
|
|
23767
|
+
const docs = collectMarvinDocs(path18.join(docsDir, subdir), knownTypes);
|
|
22655
23768
|
allDocs.push(...docs);
|
|
22656
23769
|
}
|
|
22657
23770
|
return planDocImports(allDocs, store, options);
|
|
@@ -22661,10 +23774,10 @@ function planFromDocsDirectory(classification, store, _marvinDir, options) {
|
|
|
22661
23774
|
const knownTypes = store.registeredTypes;
|
|
22662
23775
|
const allDocs = [];
|
|
22663
23776
|
allDocs.push(...collectMarvinDocs(dir, knownTypes));
|
|
22664
|
-
const entries =
|
|
23777
|
+
const entries = fs17.readdirSync(dir);
|
|
22665
23778
|
for (const entry of entries) {
|
|
22666
|
-
const entryPath =
|
|
22667
|
-
if (
|
|
23779
|
+
const entryPath = path18.join(dir, entry);
|
|
23780
|
+
if (fs17.statSync(entryPath).isDirectory()) {
|
|
22668
23781
|
allDocs.push(...collectMarvinDocs(entryPath, knownTypes));
|
|
22669
23782
|
}
|
|
22670
23783
|
}
|
|
@@ -22673,7 +23786,7 @@ function planFromDocsDirectory(classification, store, _marvinDir, options) {
|
|
|
22673
23786
|
function planFromSingleDocument(classification, store, _marvinDir, options) {
|
|
22674
23787
|
const filePath = classification.inputPath;
|
|
22675
23788
|
const knownTypes = store.registeredTypes;
|
|
22676
|
-
const raw =
|
|
23789
|
+
const raw = fs17.readFileSync(filePath, "utf-8");
|
|
22677
23790
|
const { data, content } = matter5(raw);
|
|
22678
23791
|
if (!isValidMarvinDocument(data, knownTypes)) {
|
|
22679
23792
|
return [];
|
|
@@ -22689,14 +23802,14 @@ function planFromSingleDocument(classification, store, _marvinDir, options) {
|
|
|
22689
23802
|
}
|
|
22690
23803
|
function planFromRawSourceDir(classification, marvinDir) {
|
|
22691
23804
|
const dir = classification.inputPath;
|
|
22692
|
-
const sourcesDir =
|
|
23805
|
+
const sourcesDir = path18.join(marvinDir, "sources");
|
|
22693
23806
|
const items = [];
|
|
22694
|
-
const files =
|
|
22695
|
-
const stat =
|
|
23807
|
+
const files = fs17.readdirSync(dir).filter((f) => {
|
|
23808
|
+
const stat = fs17.statSync(path18.join(dir, f));
|
|
22696
23809
|
return stat.isFile();
|
|
22697
23810
|
});
|
|
22698
23811
|
for (const file2 of files) {
|
|
22699
|
-
const sourcePath =
|
|
23812
|
+
const sourcePath = path18.join(dir, file2);
|
|
22700
23813
|
const targetPath = resolveSourceFileName(sourcesDir, file2);
|
|
22701
23814
|
items.push({
|
|
22702
23815
|
action: "copy",
|
|
@@ -22707,8 +23820,8 @@ function planFromRawSourceDir(classification, marvinDir) {
|
|
|
22707
23820
|
return items;
|
|
22708
23821
|
}
|
|
22709
23822
|
function planFromRawSourceFile(classification, marvinDir) {
|
|
22710
|
-
const sourcesDir =
|
|
22711
|
-
const fileName =
|
|
23823
|
+
const sourcesDir = path18.join(marvinDir, "sources");
|
|
23824
|
+
const fileName = path18.basename(classification.inputPath);
|
|
22712
23825
|
const targetPath = resolveSourceFileName(sourcesDir, fileName);
|
|
22713
23826
|
return [
|
|
22714
23827
|
{
|
|
@@ -22719,25 +23832,25 @@ function planFromRawSourceFile(classification, marvinDir) {
|
|
|
22719
23832
|
];
|
|
22720
23833
|
}
|
|
22721
23834
|
function resolveSourceFileName(sourcesDir, fileName) {
|
|
22722
|
-
const targetPath =
|
|
22723
|
-
if (!
|
|
23835
|
+
const targetPath = path18.join(sourcesDir, fileName);
|
|
23836
|
+
if (!fs17.existsSync(targetPath)) {
|
|
22724
23837
|
return targetPath;
|
|
22725
23838
|
}
|
|
22726
|
-
const ext =
|
|
22727
|
-
const base =
|
|
23839
|
+
const ext = path18.extname(fileName);
|
|
23840
|
+
const base = path18.basename(fileName, ext);
|
|
22728
23841
|
let counter = 1;
|
|
22729
23842
|
let candidate;
|
|
22730
23843
|
do {
|
|
22731
|
-
candidate =
|
|
23844
|
+
candidate = path18.join(sourcesDir, `${base}-${counter}${ext}`);
|
|
22732
23845
|
counter++;
|
|
22733
|
-
} while (
|
|
23846
|
+
} while (fs17.existsSync(candidate));
|
|
22734
23847
|
return candidate;
|
|
22735
23848
|
}
|
|
22736
23849
|
|
|
22737
23850
|
// src/cli/commands/import.ts
|
|
22738
23851
|
async function importCommand(inputPath, options) {
|
|
22739
|
-
const resolved =
|
|
22740
|
-
if (!
|
|
23852
|
+
const resolved = path19.resolve(inputPath);
|
|
23853
|
+
if (!fs18.existsSync(resolved)) {
|
|
22741
23854
|
throw new ImportError(`Path not found: ${resolved}`);
|
|
22742
23855
|
}
|
|
22743
23856
|
const project = loadProject();
|
|
@@ -22789,7 +23902,7 @@ async function importCommand(inputPath, options) {
|
|
|
22789
23902
|
console.log(chalk11.bold("\nStarting ingest of copied sources...\n"));
|
|
22790
23903
|
const manifest = new SourceManifestManager(marvinDir);
|
|
22791
23904
|
manifest.scan();
|
|
22792
|
-
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));
|
|
22793
23906
|
for (const fileName of copiedFileNames) {
|
|
22794
23907
|
try {
|
|
22795
23908
|
await ingestFile({
|
|
@@ -23678,14 +24791,14 @@ async function webCommand(options) {
|
|
|
23678
24791
|
}
|
|
23679
24792
|
|
|
23680
24793
|
// src/cli/commands/generate.ts
|
|
23681
|
-
import * as
|
|
23682
|
-
import * as
|
|
24794
|
+
import * as fs19 from "fs";
|
|
24795
|
+
import * as path20 from "path";
|
|
23683
24796
|
import chalk18 from "chalk";
|
|
23684
24797
|
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
23685
24798
|
async function generateClaudeMdCommand(options) {
|
|
23686
24799
|
const project = loadProject();
|
|
23687
|
-
const filePath =
|
|
23688
|
-
if (
|
|
24800
|
+
const filePath = path20.join(project.marvinDir, "CLAUDE.md");
|
|
24801
|
+
if (fs19.existsSync(filePath) && !options.force) {
|
|
23689
24802
|
const overwrite = await confirm2({
|
|
23690
24803
|
message: ".marvin/CLAUDE.md already exists. Overwrite?",
|
|
23691
24804
|
default: false
|
|
@@ -23695,7 +24808,7 @@ async function generateClaudeMdCommand(options) {
|
|
|
23695
24808
|
return;
|
|
23696
24809
|
}
|
|
23697
24810
|
}
|
|
23698
|
-
|
|
24811
|
+
fs19.writeFileSync(
|
|
23699
24812
|
filePath,
|
|
23700
24813
|
getDefaultClaudeMdContent(project.config.name),
|
|
23701
24814
|
"utf-8"
|
|
@@ -23708,7 +24821,7 @@ function createProgram() {
|
|
|
23708
24821
|
const program = new Command();
|
|
23709
24822
|
program.name("marvin").description(
|
|
23710
24823
|
"AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
|
|
23711
|
-
).version("0.4.
|
|
24824
|
+
).version("0.4.4");
|
|
23712
24825
|
program.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
|
|
23713
24826
|
await initCommand();
|
|
23714
24827
|
});
|