mrvn-cli 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -0
- package/dist/index.js +897 -205
- package/dist/index.js.map +1 -1
- package/dist/marvin-serve.js +827 -202
- package/dist/marvin-serve.js.map +1 -1
- package/dist/marvin.js +897 -205
- package/dist/marvin.js.map +1 -1
- package/package.json +1 -1
package/dist/marvin-serve.js
CHANGED
|
@@ -6474,13 +6474,13 @@ var error16 = () => {
|
|
|
6474
6474
|
// no unit
|
|
6475
6475
|
};
|
|
6476
6476
|
const typeEntry = (t) => t ? TypeNames[t] : void 0;
|
|
6477
|
-
const
|
|
6477
|
+
const typeLabel5 = (t) => {
|
|
6478
6478
|
const e = typeEntry(t);
|
|
6479
6479
|
if (e)
|
|
6480
6480
|
return e.label;
|
|
6481
6481
|
return t ?? TypeNames.unknown.label;
|
|
6482
6482
|
};
|
|
6483
|
-
const withDefinite = (t) => `\u05D4${
|
|
6483
|
+
const withDefinite = (t) => `\u05D4${typeLabel5(t)}`;
|
|
6484
6484
|
const verbFor = (t) => {
|
|
6485
6485
|
const e = typeEntry(t);
|
|
6486
6486
|
const gender = e?.gender ?? "m";
|
|
@@ -6530,7 +6530,7 @@ var error16 = () => {
|
|
|
6530
6530
|
switch (issue2.code) {
|
|
6531
6531
|
case "invalid_type": {
|
|
6532
6532
|
const expectedKey = issue2.expected;
|
|
6533
|
-
const expected = TypeDictionary[expectedKey ?? ""] ??
|
|
6533
|
+
const expected = TypeDictionary[expectedKey ?? ""] ?? typeLabel5(expectedKey);
|
|
6534
6534
|
const receivedType = parsedType(issue2.input);
|
|
6535
6535
|
const received = TypeDictionary[receivedType] ?? TypeNames[receivedType]?.label ?? receivedType;
|
|
6536
6536
|
if (/^[A-Z]/.test(issue2.expected)) {
|
|
@@ -14209,6 +14209,26 @@ config(en_default());
|
|
|
14209
14209
|
|
|
14210
14210
|
// src/agent/tools/decisions.ts
|
|
14211
14211
|
import { tool } from "@anthropic-ai/claude-agent-sdk";
|
|
14212
|
+
|
|
14213
|
+
// src/personas/owner.ts
|
|
14214
|
+
var OWNER_SHORT = ["po", "dm", "tl"];
|
|
14215
|
+
var OWNER_LONG = ["product-owner", "delivery-manager", "tech-lead"];
|
|
14216
|
+
var VALID_OWNERS = [...OWNER_SHORT, ...OWNER_LONG];
|
|
14217
|
+
var LONG_TO_SHORT = {
|
|
14218
|
+
"product-owner": "po",
|
|
14219
|
+
"delivery-manager": "dm",
|
|
14220
|
+
"tech-lead": "tl"
|
|
14221
|
+
};
|
|
14222
|
+
var ownerSchema = external_exports.enum(VALID_OWNERS);
|
|
14223
|
+
function normalizeOwner(owner) {
|
|
14224
|
+
if (owner === void 0) return void 0;
|
|
14225
|
+
return LONG_TO_SHORT[owner] ?? owner;
|
|
14226
|
+
}
|
|
14227
|
+
function isValidOwner(value) {
|
|
14228
|
+
return VALID_OWNERS.includes(value);
|
|
14229
|
+
}
|
|
14230
|
+
|
|
14231
|
+
// src/agent/tools/decisions.ts
|
|
14212
14232
|
function createDecisionTools(store) {
|
|
14213
14233
|
return [
|
|
14214
14234
|
tool(
|
|
@@ -14263,7 +14283,8 @@ function createDecisionTools(store) {
|
|
|
14263
14283
|
title: external_exports.string().describe("Title of the decision"),
|
|
14264
14284
|
content: external_exports.string().describe("Decision description, context, and rationale"),
|
|
14265
14285
|
status: external_exports.enum(["open", "decided", "superseded", "dismissed"]).optional().describe("Status (default: 'open')"),
|
|
14266
|
-
owner:
|
|
14286
|
+
owner: ownerSchema.optional().describe("Persona role responsible (po, dm, tl)"),
|
|
14287
|
+
assignee: external_exports.string().optional().describe("Person assigned to do the work"),
|
|
14267
14288
|
tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization")
|
|
14268
14289
|
},
|
|
14269
14290
|
async (args) => {
|
|
@@ -14272,7 +14293,8 @@ function createDecisionTools(store) {
|
|
|
14272
14293
|
{
|
|
14273
14294
|
title: args.title,
|
|
14274
14295
|
status: args.status,
|
|
14275
|
-
owner: args.owner,
|
|
14296
|
+
owner: normalizeOwner(args.owner),
|
|
14297
|
+
assignee: args.assignee,
|
|
14276
14298
|
tags: args.tags
|
|
14277
14299
|
},
|
|
14278
14300
|
args.content
|
|
@@ -14295,11 +14317,14 @@ function createDecisionTools(store) {
|
|
|
14295
14317
|
title: external_exports.string().optional().describe("New title"),
|
|
14296
14318
|
status: external_exports.enum(["open", "decided", "superseded", "dismissed"]).optional().describe("New status"),
|
|
14297
14319
|
content: external_exports.string().optional().describe("New content"),
|
|
14298
|
-
owner:
|
|
14320
|
+
owner: ownerSchema.optional().describe("Persona role responsible (po, dm, tl)"),
|
|
14321
|
+
assignee: external_exports.string().optional().describe("Person assigned to do the work"),
|
|
14299
14322
|
tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove 'risk', add 'risk-mitigated')")
|
|
14300
14323
|
},
|
|
14301
14324
|
async (args) => {
|
|
14302
|
-
const { id, content, ...updates } = args;
|
|
14325
|
+
const { id, content, owner, assignee, ...updates } = args;
|
|
14326
|
+
if (owner !== void 0) updates.owner = normalizeOwner(owner);
|
|
14327
|
+
if (assignee !== void 0) updates.assignee = assignee;
|
|
14303
14328
|
const doc = store.update(id, updates, content);
|
|
14304
14329
|
return {
|
|
14305
14330
|
content: [
|
|
@@ -14334,7 +14359,7 @@ function propagateProgressFromTask(store, taskId) {
|
|
|
14334
14359
|
store.update(taskId, { progress: 100 });
|
|
14335
14360
|
updated.push(taskId);
|
|
14336
14361
|
}
|
|
14337
|
-
} else {
|
|
14362
|
+
} else if (!task.frontmatter.progressOverride) {
|
|
14338
14363
|
const children = store.list({ type: "contribution" }).filter((d) => d.frontmatter.aboutArtifact === taskId);
|
|
14339
14364
|
if (children.length > 0) {
|
|
14340
14365
|
const avg = children.reduce((sum, c) => sum + getEffectiveProgress(c.frontmatter), 0) / children.length;
|
|
@@ -14365,27 +14390,29 @@ function propagateProgressToAction(store, actionId) {
|
|
|
14365
14390
|
}
|
|
14366
14391
|
return updated;
|
|
14367
14392
|
}
|
|
14368
|
-
|
|
14369
|
-
|
|
14370
|
-
|
|
14371
|
-
|
|
14372
|
-
|
|
14373
|
-
|
|
14374
|
-
|
|
14375
|
-
|
|
14376
|
-
|
|
14377
|
-
|
|
14378
|
-
|
|
14379
|
-
|
|
14380
|
-
|
|
14381
|
-
|
|
14382
|
-
|
|
14383
|
-
|
|
14384
|
-
|
|
14385
|
-
|
|
14386
|
-
|
|
14387
|
-
|
|
14388
|
-
|
|
14393
|
+
if (!action.frontmatter.progressOverride) {
|
|
14394
|
+
const childTasks = store.list({ type: "task" }).filter((d) => d.frontmatter.aboutArtifact === actionId);
|
|
14395
|
+
const directContribs = store.list({ type: "contribution" }).filter((d) => d.frontmatter.aboutArtifact === actionId);
|
|
14396
|
+
const hasTasks = childTasks.length > 0;
|
|
14397
|
+
const hasContribs = directContribs.length > 0;
|
|
14398
|
+
let progress;
|
|
14399
|
+
if (hasTasks && hasContribs) {
|
|
14400
|
+
const taskAvg = childTasks.reduce((s, t) => s + getEffectiveProgress(t.frontmatter), 0) / childTasks.length;
|
|
14401
|
+
const contribAvg = directContribs.reduce((s, c) => s + getEffectiveProgress(c.frontmatter), 0) / directContribs.length;
|
|
14402
|
+
progress = Math.round(taskAvg * 0.8 + contribAvg * 0.2);
|
|
14403
|
+
} else if (hasTasks) {
|
|
14404
|
+
progress = Math.round(
|
|
14405
|
+
childTasks.reduce((s, t) => s + getEffectiveProgress(t.frontmatter), 0) / childTasks.length
|
|
14406
|
+
);
|
|
14407
|
+
} else if (hasContribs) {
|
|
14408
|
+
progress = Math.round(
|
|
14409
|
+
directContribs.reduce((s, c) => s + getEffectiveProgress(c.frontmatter), 0) / directContribs.length
|
|
14410
|
+
);
|
|
14411
|
+
}
|
|
14412
|
+
if (progress !== void 0) {
|
|
14413
|
+
store.update(actionId, { progress });
|
|
14414
|
+
updated.push(actionId);
|
|
14415
|
+
}
|
|
14389
14416
|
}
|
|
14390
14417
|
return updated;
|
|
14391
14418
|
}
|
|
@@ -14480,12 +14507,13 @@ function createActionTools(store) {
|
|
|
14480
14507
|
title: external_exports.string().describe("Title of the action item"),
|
|
14481
14508
|
content: external_exports.string().describe("Description of what needs to be done"),
|
|
14482
14509
|
status: external_exports.string().optional().describe("Status (default: 'open')"),
|
|
14483
|
-
owner:
|
|
14510
|
+
owner: ownerSchema.optional().describe("Persona role responsible (po, dm, tl)"),
|
|
14511
|
+
assignee: external_exports.string().optional().describe("Person assigned to do the work"),
|
|
14484
14512
|
priority: external_exports.string().optional().describe("Priority (high, medium, low)"),
|
|
14485
14513
|
tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization"),
|
|
14486
14514
|
dueDate: external_exports.string().optional().describe("Due date in ISO format (e.g. '2026-03-15')"),
|
|
14487
14515
|
sprints: external_exports.array(external_exports.string()).optional().describe("Sprint IDs to assign (e.g. ['SP-001']). Adds sprint:SP-xxx tags."),
|
|
14488
|
-
|
|
14516
|
+
workFocus: external_exports.string().optional().describe("Work focus name (e.g. 'Budget UX'). Adds a focus:<value> tag.")
|
|
14489
14517
|
},
|
|
14490
14518
|
async (args) => {
|
|
14491
14519
|
const tags = [...args.tags ?? []];
|
|
@@ -14495,15 +14523,16 @@ function createActionTools(store) {
|
|
|
14495
14523
|
if (!tags.includes(tag)) tags.push(tag);
|
|
14496
14524
|
}
|
|
14497
14525
|
}
|
|
14498
|
-
if (args.
|
|
14499
|
-
tags.push(`
|
|
14526
|
+
if (args.workFocus) {
|
|
14527
|
+
tags.push(`focus:${args.workFocus}`);
|
|
14500
14528
|
}
|
|
14501
14529
|
const doc = store.create(
|
|
14502
14530
|
"action",
|
|
14503
14531
|
{
|
|
14504
14532
|
title: args.title,
|
|
14505
14533
|
status: args.status,
|
|
14506
|
-
owner: args.owner,
|
|
14534
|
+
owner: normalizeOwner(args.owner),
|
|
14535
|
+
assignee: args.assignee,
|
|
14507
14536
|
priority: args.priority,
|
|
14508
14537
|
tags: tags.length > 0 ? tags : void 0,
|
|
14509
14538
|
dueDate: args.dueDate
|
|
@@ -14531,16 +14560,19 @@ function createActionTools(store) {
|
|
|
14531
14560
|
title: external_exports.string().optional().describe("New title"),
|
|
14532
14561
|
status: external_exports.string().optional().describe("New status"),
|
|
14533
14562
|
content: external_exports.string().optional().describe("New content"),
|
|
14534
|
-
owner:
|
|
14563
|
+
owner: ownerSchema.optional().describe("Persona role responsible (po, dm, tl)"),
|
|
14564
|
+
assignee: external_exports.string().optional().describe("Person assigned to do the work"),
|
|
14535
14565
|
priority: external_exports.string().optional().describe("New priority"),
|
|
14536
14566
|
dueDate: external_exports.string().optional().describe("Due date in ISO format (e.g. '2026-03-15')"),
|
|
14537
14567
|
tags: external_exports.array(external_exports.string()).optional().describe("Replace all tags. When provided with sprints, sprint tags are merged into this array."),
|
|
14538
14568
|
sprints: external_exports.array(external_exports.string()).optional().describe("Sprint IDs to assign (replaces existing sprint tags). E.g. ['SP-001']."),
|
|
14539
|
-
|
|
14569
|
+
workFocus: external_exports.string().optional().describe("Work focus name (e.g. 'Budget UX'). Replaces existing focus:<value> tag."),
|
|
14540
14570
|
progress: external_exports.number().optional().describe("Explicit progress percentage (0-100).")
|
|
14541
14571
|
},
|
|
14542
14572
|
async (args) => {
|
|
14543
|
-
const { id, content, sprints, tags,
|
|
14573
|
+
const { id, content, sprints, tags, workFocus, progress, owner, assignee, ...updates } = args;
|
|
14574
|
+
if (owner !== void 0) updates.owner = normalizeOwner(owner);
|
|
14575
|
+
if (assignee !== void 0) updates.assignee = assignee;
|
|
14544
14576
|
if (tags !== void 0) {
|
|
14545
14577
|
const merged = [...tags];
|
|
14546
14578
|
if (sprints) {
|
|
@@ -14549,14 +14581,14 @@ function createActionTools(store) {
|
|
|
14549
14581
|
if (!merged.includes(tag)) merged.push(tag);
|
|
14550
14582
|
}
|
|
14551
14583
|
}
|
|
14552
|
-
if (
|
|
14553
|
-
const filtered = merged.filter((t) => !t.startsWith("
|
|
14554
|
-
filtered.push(`
|
|
14584
|
+
if (workFocus !== void 0) {
|
|
14585
|
+
const filtered = merged.filter((t) => !t.startsWith("focus:"));
|
|
14586
|
+
filtered.push(`focus:${workFocus}`);
|
|
14555
14587
|
updates.tags = filtered;
|
|
14556
14588
|
} else {
|
|
14557
14589
|
updates.tags = merged;
|
|
14558
14590
|
}
|
|
14559
|
-
} else if (sprints !== void 0 ||
|
|
14591
|
+
} else if (sprints !== void 0 || workFocus !== void 0) {
|
|
14560
14592
|
const existing = store.get(id);
|
|
14561
14593
|
if (!existing) {
|
|
14562
14594
|
return {
|
|
@@ -14569,14 +14601,15 @@ function createActionTools(store) {
|
|
|
14569
14601
|
existingTags = existingTags.filter((t) => !t.startsWith("sprint:"));
|
|
14570
14602
|
existingTags.push(...sprints.map((s) => `sprint:${s}`));
|
|
14571
14603
|
}
|
|
14572
|
-
if (
|
|
14573
|
-
existingTags = existingTags.filter((t) => !t.startsWith("
|
|
14574
|
-
existingTags.push(`
|
|
14604
|
+
if (workFocus !== void 0) {
|
|
14605
|
+
existingTags = existingTags.filter((t) => !t.startsWith("focus:"));
|
|
14606
|
+
existingTags.push(`focus:${workFocus}`);
|
|
14575
14607
|
}
|
|
14576
14608
|
updates.tags = existingTags;
|
|
14577
14609
|
}
|
|
14578
14610
|
if (typeof progress === "number") {
|
|
14579
14611
|
updates.progress = Math.max(0, Math.min(100, Math.round(progress)));
|
|
14612
|
+
updates.progressOverride = true;
|
|
14580
14613
|
}
|
|
14581
14614
|
const doc = store.update(id, updates, content);
|
|
14582
14615
|
if (args.status !== void 0 || typeof progress === "number") {
|
|
@@ -14683,7 +14716,8 @@ function createQuestionTools(store) {
|
|
|
14683
14716
|
title: external_exports.string().describe("The question being asked"),
|
|
14684
14717
|
content: external_exports.string().describe("Context and details about the question"),
|
|
14685
14718
|
status: external_exports.string().optional().describe("Status (default: 'open')"),
|
|
14686
|
-
owner:
|
|
14719
|
+
owner: ownerSchema.optional().describe("Persona role responsible (po, dm, tl)"),
|
|
14720
|
+
assignee: external_exports.string().optional().describe("Person assigned to do the work"),
|
|
14687
14721
|
tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization")
|
|
14688
14722
|
},
|
|
14689
14723
|
async (args) => {
|
|
@@ -14692,7 +14726,8 @@ function createQuestionTools(store) {
|
|
|
14692
14726
|
{
|
|
14693
14727
|
title: args.title,
|
|
14694
14728
|
status: args.status,
|
|
14695
|
-
owner: args.owner,
|
|
14729
|
+
owner: normalizeOwner(args.owner),
|
|
14730
|
+
assignee: args.assignee,
|
|
14696
14731
|
tags: args.tags
|
|
14697
14732
|
},
|
|
14698
14733
|
args.content
|
|
@@ -14715,11 +14750,14 @@ function createQuestionTools(store) {
|
|
|
14715
14750
|
title: external_exports.string().optional().describe("New title"),
|
|
14716
14751
|
status: external_exports.string().optional().describe("New status (e.g. 'answered')"),
|
|
14717
14752
|
content: external_exports.string().optional().describe("Updated content / answer"),
|
|
14718
|
-
owner:
|
|
14753
|
+
owner: ownerSchema.optional().describe("Persona role responsible (po, dm, tl)"),
|
|
14754
|
+
assignee: external_exports.string().optional().describe("Person assigned to do the work"),
|
|
14719
14755
|
tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove 'risk', add 'risk-mitigated')")
|
|
14720
14756
|
},
|
|
14721
14757
|
async (args) => {
|
|
14722
|
-
const { id, content, ...updates } = args;
|
|
14758
|
+
const { id, content, owner, assignee, ...updates } = args;
|
|
14759
|
+
if (owner !== void 0) updates.owner = normalizeOwner(owner);
|
|
14760
|
+
if (assignee !== void 0) updates.assignee = assignee;
|
|
14723
14761
|
const doc = store.update(id, updates, content);
|
|
14724
14762
|
return {
|
|
14725
14763
|
content: [
|
|
@@ -14744,18 +14782,20 @@ function createDocumentTools(store) {
|
|
|
14744
14782
|
{
|
|
14745
14783
|
type: external_exports.string().optional().describe(`Filter by document type (registered types: ${store.registeredTypes.join(", ")})`),
|
|
14746
14784
|
status: external_exports.string().optional().describe("Filter by status"),
|
|
14785
|
+
owner: external_exports.string().optional().describe("Filter by persona role owner (po, dm, tl)"),
|
|
14747
14786
|
tag: external_exports.string().optional().describe("Filter by tag"),
|
|
14748
|
-
|
|
14787
|
+
workFocus: external_exports.string().optional().describe("Filter by work focus name (matches focus:<value> tag)")
|
|
14749
14788
|
},
|
|
14750
14789
|
async (args) => {
|
|
14751
14790
|
let docs = store.list({
|
|
14752
14791
|
type: args.type,
|
|
14753
14792
|
status: args.status,
|
|
14793
|
+
owner: args.owner,
|
|
14754
14794
|
tag: args.tag
|
|
14755
14795
|
});
|
|
14756
|
-
if (args.
|
|
14757
|
-
const
|
|
14758
|
-
docs = docs.filter((d) => d.frontmatter.tags?.includes(
|
|
14796
|
+
if (args.workFocus) {
|
|
14797
|
+
const focusTag = `focus:${args.workFocus}`;
|
|
14798
|
+
docs = docs.filter((d) => d.frontmatter.tags?.includes(focusTag));
|
|
14759
14799
|
}
|
|
14760
14800
|
const summary = docs.map((d) => ({
|
|
14761
14801
|
id: d.frontmatter.id,
|
|
@@ -15077,7 +15117,8 @@ function createMeetingTools(store) {
|
|
|
15077
15117
|
title: external_exports.string().describe("Title of the meeting"),
|
|
15078
15118
|
content: external_exports.string().describe("Meeting agenda, notes, or minutes"),
|
|
15079
15119
|
status: external_exports.string().optional().describe("Status (default: 'scheduled')"),
|
|
15080
|
-
owner:
|
|
15120
|
+
owner: ownerSchema.optional().describe("Persona role responsible (po, dm, tl)"),
|
|
15121
|
+
assignee: external_exports.string().optional().describe("Person assigned to do the work"),
|
|
15081
15122
|
tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization"),
|
|
15082
15123
|
attendees: external_exports.array(external_exports.string()).optional().describe("List of attendees"),
|
|
15083
15124
|
date: external_exports.string().describe("Date the meeting took place (ISO format, e.g. '2025-01-15'). Extract from the meeting content. If not found, ask the user before calling this tool.")
|
|
@@ -15087,7 +15128,8 @@ function createMeetingTools(store) {
|
|
|
15087
15128
|
title: args.title,
|
|
15088
15129
|
status: args.status ?? "scheduled"
|
|
15089
15130
|
};
|
|
15090
|
-
if (args.owner) frontmatter.owner = args.owner;
|
|
15131
|
+
if (args.owner) frontmatter.owner = normalizeOwner(args.owner);
|
|
15132
|
+
if (args.assignee) frontmatter.assignee = args.assignee;
|
|
15091
15133
|
if (args.tags) frontmatter.tags = args.tags;
|
|
15092
15134
|
if (args.attendees) frontmatter.attendees = args.attendees;
|
|
15093
15135
|
frontmatter.date = args.date;
|
|
@@ -15114,10 +15156,13 @@ function createMeetingTools(store) {
|
|
|
15114
15156
|
title: external_exports.string().optional().describe("New title"),
|
|
15115
15157
|
status: external_exports.string().optional().describe("New status"),
|
|
15116
15158
|
content: external_exports.string().optional().describe("New content"),
|
|
15117
|
-
owner:
|
|
15159
|
+
owner: ownerSchema.optional().describe("Persona role responsible (po, dm, tl)"),
|
|
15160
|
+
assignee: external_exports.string().optional().describe("Person assigned to do the work")
|
|
15118
15161
|
},
|
|
15119
15162
|
async (args) => {
|
|
15120
|
-
const { id, content, ...updates } = args;
|
|
15163
|
+
const { id, content, owner, assignee, ...updates } = args;
|
|
15164
|
+
if (owner !== void 0) updates.owner = normalizeOwner(owner);
|
|
15165
|
+
if (assignee !== void 0) updates.assignee = assignee;
|
|
15121
15166
|
const doc = store.update(id, updates, content);
|
|
15122
15167
|
return {
|
|
15123
15168
|
content: [
|
|
@@ -15650,14 +15695,14 @@ function collectSprintSummaryData(store, sprintId) {
|
|
|
15650
15695
|
const sprintItemIds = new Set(workItemDocs.map((d) => d.frontmatter.id));
|
|
15651
15696
|
for (const doc of workItemDocs) {
|
|
15652
15697
|
const about = doc.frontmatter.aboutArtifact;
|
|
15653
|
-
const
|
|
15698
|
+
const focusTag = (doc.frontmatter.tags ?? []).find((t) => t.startsWith("focus:"));
|
|
15654
15699
|
const item = {
|
|
15655
15700
|
id: doc.frontmatter.id,
|
|
15656
15701
|
title: doc.frontmatter.title,
|
|
15657
15702
|
type: doc.frontmatter.type,
|
|
15658
15703
|
status: doc.frontmatter.status,
|
|
15659
15704
|
progress: getEffectiveProgress(doc.frontmatter),
|
|
15660
|
-
|
|
15705
|
+
workFocus: focusTag ? focusTag.slice(6) : void 0,
|
|
15661
15706
|
aboutArtifact: about
|
|
15662
15707
|
};
|
|
15663
15708
|
allItemsById.set(item.id, item);
|
|
@@ -16812,7 +16857,8 @@ function createFeatureTools(store) {
|
|
|
16812
16857
|
title: external_exports.string().describe("Feature title"),
|
|
16813
16858
|
content: external_exports.string().describe("Feature description and requirements"),
|
|
16814
16859
|
status: external_exports.enum(["draft", "approved", "deferred", "done"]).optional().describe("Feature status (default: 'draft')"),
|
|
16815
|
-
owner:
|
|
16860
|
+
owner: ownerSchema.optional().describe("Persona role responsible (po, dm, tl)"),
|
|
16861
|
+
assignee: external_exports.string().optional().describe("Person assigned to do the work"),
|
|
16816
16862
|
priority: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("Feature priority"),
|
|
16817
16863
|
tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization")
|
|
16818
16864
|
},
|
|
@@ -16821,7 +16867,8 @@ function createFeatureTools(store) {
|
|
|
16821
16867
|
title: args.title,
|
|
16822
16868
|
status: args.status ?? "draft"
|
|
16823
16869
|
};
|
|
16824
|
-
if (args.owner) frontmatter.owner = args.owner;
|
|
16870
|
+
if (args.owner) frontmatter.owner = normalizeOwner(args.owner);
|
|
16871
|
+
if (args.assignee) frontmatter.assignee = args.assignee;
|
|
16825
16872
|
if (args.priority) frontmatter.priority = args.priority;
|
|
16826
16873
|
if (args.tags) frontmatter.tags = args.tags;
|
|
16827
16874
|
const doc = store.create("feature", frontmatter, args.content);
|
|
@@ -16843,12 +16890,15 @@ function createFeatureTools(store) {
|
|
|
16843
16890
|
title: external_exports.string().optional().describe("New title"),
|
|
16844
16891
|
status: external_exports.enum(["draft", "approved", "deferred", "done"]).optional().describe("New status"),
|
|
16845
16892
|
content: external_exports.string().optional().describe("New content"),
|
|
16846
|
-
owner:
|
|
16893
|
+
owner: ownerSchema.optional().describe("Persona role responsible (po, dm, tl)"),
|
|
16894
|
+
assignee: external_exports.string().optional().describe("Person assigned to do the work"),
|
|
16847
16895
|
priority: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("New priority"),
|
|
16848
16896
|
tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove 'risk', add 'risk-mitigated')")
|
|
16849
16897
|
},
|
|
16850
16898
|
async (args) => {
|
|
16851
|
-
const { id, content, ...updates } = args;
|
|
16899
|
+
const { id, content, owner, assignee, ...updates } = args;
|
|
16900
|
+
if (owner !== void 0) updates.owner = normalizeOwner(owner);
|
|
16901
|
+
if (assignee !== void 0) updates.assignee = assignee;
|
|
16852
16902
|
const doc = store.update(id, updates, content);
|
|
16853
16903
|
return {
|
|
16854
16904
|
content: [
|
|
@@ -16946,7 +16996,8 @@ function createEpicTools(store) {
|
|
|
16946
16996
|
content: external_exports.string().describe("Epic description and scope"),
|
|
16947
16997
|
linkedFeature: linkedFeatureArray.describe("Feature ID(s) to link this epic to (e.g. ['F-001'] or ['F-001', 'F-002'])"),
|
|
16948
16998
|
status: external_exports.enum(["planned", "in-progress", "done"]).optional().describe("Epic status (default: 'planned')"),
|
|
16949
|
-
owner:
|
|
16999
|
+
owner: ownerSchema.optional().describe("Persona role responsible (po, dm, tl)"),
|
|
17000
|
+
assignee: external_exports.string().optional().describe("Person assigned to do the work"),
|
|
16950
17001
|
targetDate: external_exports.string().optional().describe("Target completion date (ISO format)"),
|
|
16951
17002
|
estimatedEffort: external_exports.string().optional().describe("Estimated effort (e.g. '2 weeks', '5 story points')"),
|
|
16952
17003
|
tags: external_exports.array(external_exports.string()).optional().describe("Additional tags")
|
|
@@ -16995,7 +17046,8 @@ function createEpicTools(store) {
|
|
|
16995
17046
|
linkedFeature: linkedFeatures,
|
|
16996
17047
|
tags: [...generateFeatureTags(linkedFeatures), ...args.tags ?? []]
|
|
16997
17048
|
};
|
|
16998
|
-
if (args.owner) frontmatter.owner = args.owner;
|
|
17049
|
+
if (args.owner) frontmatter.owner = normalizeOwner(args.owner);
|
|
17050
|
+
if (args.assignee) frontmatter.assignee = args.assignee;
|
|
16999
17051
|
if (args.targetDate) frontmatter.targetDate = args.targetDate;
|
|
17000
17052
|
if (args.estimatedEffort) frontmatter.estimatedEffort = args.estimatedEffort;
|
|
17001
17053
|
const doc = store.create("epic", frontmatter, args.content);
|
|
@@ -17017,14 +17069,17 @@ function createEpicTools(store) {
|
|
|
17017
17069
|
title: external_exports.string().optional().describe("New title"),
|
|
17018
17070
|
status: external_exports.enum(["planned", "in-progress", "done"]).optional().describe("New status"),
|
|
17019
17071
|
content: external_exports.string().optional().describe("New content"),
|
|
17020
|
-
owner:
|
|
17072
|
+
owner: ownerSchema.optional().describe("Persona role responsible (po, dm, tl)"),
|
|
17073
|
+
assignee: external_exports.string().optional().describe("Person assigned to do the work"),
|
|
17021
17074
|
targetDate: external_exports.string().optional().describe("New target date"),
|
|
17022
17075
|
estimatedEffort: external_exports.string().optional().describe("New estimated effort"),
|
|
17023
17076
|
linkedFeature: linkedFeatureArray.optional().describe("New linked feature ID(s)"),
|
|
17024
17077
|
tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove 'risk', add 'risk-mitigated')")
|
|
17025
17078
|
},
|
|
17026
17079
|
async (args) => {
|
|
17027
|
-
const { id, content, linkedFeature: rawLinkedFeature, tags: userTags, ...updates } = args;
|
|
17080
|
+
const { id, content, linkedFeature: rawLinkedFeature, tags: userTags, owner, assignee, ...updates } = args;
|
|
17081
|
+
if (owner !== void 0) updates.owner = normalizeOwner(owner);
|
|
17082
|
+
if (assignee !== void 0) updates.assignee = assignee;
|
|
17028
17083
|
if (rawLinkedFeature !== void 0) {
|
|
17029
17084
|
const linkedFeatures = normalizeLinkedFeatures(rawLinkedFeature);
|
|
17030
17085
|
for (const featureId of linkedFeatures) {
|
|
@@ -17151,7 +17206,7 @@ function createContributionTools(store) {
|
|
|
17151
17206
|
aboutArtifact: external_exports.string().describe("Artifact ID this contribution relates to (e.g. 'A-001', 'T-003')"),
|
|
17152
17207
|
status: external_exports.string().optional().describe("Status (default: 'done')"),
|
|
17153
17208
|
tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization"),
|
|
17154
|
-
|
|
17209
|
+
workFocus: external_exports.string().optional().describe("Work focus name (e.g. 'Budget UX'). Adds a focus:<value> tag."),
|
|
17155
17210
|
parentProgress: external_exports.number().optional().describe("Set progress (0-100) on the parent artifact (e.g. task or action). Propagates up the hierarchy.")
|
|
17156
17211
|
},
|
|
17157
17212
|
async (args) => {
|
|
@@ -17163,7 +17218,7 @@ function createContributionTools(store) {
|
|
|
17163
17218
|
};
|
|
17164
17219
|
frontmatter.aboutArtifact = args.aboutArtifact;
|
|
17165
17220
|
const tags = [...args.tags ?? []];
|
|
17166
|
-
if (args.
|
|
17221
|
+
if (args.workFocus) tags.push(`focus:${args.workFocus}`);
|
|
17167
17222
|
if (tags.length > 0) frontmatter.tags = tags;
|
|
17168
17223
|
const doc = store.create("contribution", frontmatter, args.content);
|
|
17169
17224
|
const progressParts = [];
|
|
@@ -17172,7 +17227,7 @@ function createContributionTools(store) {
|
|
|
17172
17227
|
if (parent) {
|
|
17173
17228
|
if (typeof args.parentProgress === "number") {
|
|
17174
17229
|
const clamped = Math.max(0, Math.min(100, Math.round(args.parentProgress)));
|
|
17175
|
-
store.update(args.aboutArtifact, { progress: clamped });
|
|
17230
|
+
store.update(args.aboutArtifact, { progress: clamped, progressOverride: true });
|
|
17176
17231
|
progressParts.push(`${args.aboutArtifact} \u2192 ${clamped}%`);
|
|
17177
17232
|
if (parent.frontmatter.type === "task") {
|
|
17178
17233
|
const grandparent = parent.frontmatter.aboutArtifact;
|
|
@@ -17219,15 +17274,15 @@ function createContributionTools(store) {
|
|
|
17219
17274
|
title: external_exports.string().optional().describe("New title"),
|
|
17220
17275
|
status: external_exports.string().optional().describe("New status"),
|
|
17221
17276
|
content: external_exports.string().optional().describe("New content"),
|
|
17222
|
-
|
|
17277
|
+
workFocus: external_exports.string().optional().describe("Work focus name (e.g. 'Budget UX'). Replaces existing focus:<value> tag.")
|
|
17223
17278
|
},
|
|
17224
17279
|
async (args) => {
|
|
17225
|
-
const { id, content,
|
|
17226
|
-
if (
|
|
17280
|
+
const { id, content, workFocus, ...updates } = args;
|
|
17281
|
+
if (workFocus !== void 0) {
|
|
17227
17282
|
const existing = store.get(id);
|
|
17228
17283
|
const existingTags = existing?.frontmatter.tags ?? [];
|
|
17229
|
-
const filtered = existingTags.filter((t) => !t.startsWith("
|
|
17230
|
-
filtered.push(`
|
|
17284
|
+
const filtered = existingTags.filter((t) => !t.startsWith("focus:"));
|
|
17285
|
+
filtered.push(`focus:${workFocus}`);
|
|
17231
17286
|
updates.tags = filtered;
|
|
17232
17287
|
}
|
|
17233
17288
|
const oldDoc = store.get(id);
|
|
@@ -17710,7 +17765,7 @@ function createTaskTools(store) {
|
|
|
17710
17765
|
complexity: external_exports.enum(["trivial", "simple", "moderate", "complex", "very-complex"]).optional().describe("Task complexity"),
|
|
17711
17766
|
priority: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("Task priority"),
|
|
17712
17767
|
tags: external_exports.array(external_exports.string()).optional().describe("Additional tags"),
|
|
17713
|
-
|
|
17768
|
+
workFocus: external_exports.string().optional().describe("Work focus name (e.g. 'Budget UX'). Adds a focus:<value> tag.")
|
|
17714
17769
|
},
|
|
17715
17770
|
async (args) => {
|
|
17716
17771
|
const linkedEpics = normalizeLinkedEpics(args.linkedEpic);
|
|
@@ -17724,7 +17779,7 @@ function createTaskTools(store) {
|
|
|
17724
17779
|
}
|
|
17725
17780
|
}
|
|
17726
17781
|
const baseTags = [...generateEpicTags(linkedEpics), ...args.tags ?? []];
|
|
17727
|
-
if (args.
|
|
17782
|
+
if (args.workFocus) baseTags.push(`focus:${args.workFocus}`);
|
|
17728
17783
|
const frontmatter = {
|
|
17729
17784
|
title: args.title,
|
|
17730
17785
|
status: args.status ?? "backlog",
|
|
@@ -17765,11 +17820,11 @@ function createTaskTools(store) {
|
|
|
17765
17820
|
complexity: external_exports.enum(["trivial", "simple", "moderate", "complex", "very-complex"]).optional().describe("New complexity"),
|
|
17766
17821
|
priority: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("New priority"),
|
|
17767
17822
|
tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove old tags, add new ones)"),
|
|
17768
|
-
|
|
17823
|
+
workFocus: external_exports.string().optional().describe("Work focus name (e.g. 'Budget UX'). Replaces existing focus:<value> tag."),
|
|
17769
17824
|
progress: external_exports.number().optional().describe("Explicit progress percentage (0-100). Overrides auto-calculation from child contributions.")
|
|
17770
17825
|
},
|
|
17771
17826
|
async (args) => {
|
|
17772
|
-
const { id, content, linkedEpic: rawLinkedEpic, tags: userTags,
|
|
17827
|
+
const { id, content, linkedEpic: rawLinkedEpic, tags: userTags, workFocus, progress, ...updates } = args;
|
|
17773
17828
|
const warnings = [];
|
|
17774
17829
|
if (rawLinkedEpic !== void 0) {
|
|
17775
17830
|
const linkedEpics = normalizeLinkedEpics(rawLinkedEpic);
|
|
@@ -17790,14 +17845,15 @@ function createTaskTools(store) {
|
|
|
17790
17845
|
} else if (userTags !== void 0) {
|
|
17791
17846
|
updates.tags = userTags;
|
|
17792
17847
|
}
|
|
17793
|
-
if (
|
|
17848
|
+
if (workFocus !== void 0) {
|
|
17794
17849
|
const currentTags = updates.tags ?? store.get(id)?.frontmatter.tags ?? [];
|
|
17795
|
-
const filtered = currentTags.filter((t) => !t.startsWith("
|
|
17796
|
-
filtered.push(`
|
|
17850
|
+
const filtered = currentTags.filter((t) => !t.startsWith("focus:"));
|
|
17851
|
+
filtered.push(`focus:${workFocus}`);
|
|
17797
17852
|
updates.tags = filtered;
|
|
17798
17853
|
}
|
|
17799
17854
|
if (typeof progress === "number") {
|
|
17800
17855
|
updates.progress = Math.max(0, Math.min(100, Math.round(progress)));
|
|
17856
|
+
updates.progressOverride = true;
|
|
17801
17857
|
}
|
|
17802
17858
|
const doc = store.update(id, updates, content);
|
|
17803
17859
|
if (args.status !== void 0 || typeof progress === "number") {
|
|
@@ -20019,7 +20075,7 @@ ${fragment}`);
|
|
|
20019
20075
|
}
|
|
20020
20076
|
|
|
20021
20077
|
// src/skills/action-tools.ts
|
|
20022
|
-
import { tool as
|
|
20078
|
+
import { tool as tool24 } from "@anthropic-ai/claude-agent-sdk";
|
|
20023
20079
|
|
|
20024
20080
|
// src/skills/action-runner.ts
|
|
20025
20081
|
import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
|
|
@@ -20074,7 +20130,7 @@ function formatDate(iso) {
|
|
|
20074
20130
|
if (!iso) return "";
|
|
20075
20131
|
return iso.slice(0, 10);
|
|
20076
20132
|
}
|
|
20077
|
-
function
|
|
20133
|
+
function typeLabel2(type) {
|
|
20078
20134
|
return type.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
20079
20135
|
}
|
|
20080
20136
|
function renderMarkdown(md) {
|
|
@@ -21829,12 +21885,45 @@ tr:hover td {
|
|
|
21829
21885
|
font-weight: 700;
|
|
21830
21886
|
color: var(--text);
|
|
21831
21887
|
}
|
|
21888
|
+
|
|
21889
|
+
/* Focus-grouped work items */
|
|
21890
|
+
.focus-row td:first-child {
|
|
21891
|
+
border-left: 3px solid var(--focus-color, var(--border));
|
|
21892
|
+
}
|
|
21893
|
+
|
|
21894
|
+
.focus-group-header td {
|
|
21895
|
+
background: var(--bg-hover);
|
|
21896
|
+
border-left: 3px solid var(--focus-color, var(--border));
|
|
21897
|
+
padding-top: 0.5rem;
|
|
21898
|
+
padding-bottom: 0.5rem;
|
|
21899
|
+
border-bottom: 1px solid var(--border);
|
|
21900
|
+
}
|
|
21901
|
+
|
|
21902
|
+
.focus-group-header td:first-child {
|
|
21903
|
+
border-left-width: 3px;
|
|
21904
|
+
}
|
|
21905
|
+
|
|
21906
|
+
.focus-group-name {
|
|
21907
|
+
font-weight: 600;
|
|
21908
|
+
font-size: 0.8rem;
|
|
21909
|
+
color: var(--text);
|
|
21910
|
+
margin-right: 0.75rem;
|
|
21911
|
+
}
|
|
21912
|
+
|
|
21913
|
+
.focus-group-stats {
|
|
21914
|
+
font-size: 0.75rem;
|
|
21915
|
+
color: var(--text-dim);
|
|
21916
|
+
}
|
|
21917
|
+
|
|
21918
|
+
.focus-group-progress {
|
|
21919
|
+
width: 96px;
|
|
21920
|
+
}
|
|
21832
21921
|
`;
|
|
21833
21922
|
}
|
|
21834
21923
|
|
|
21835
21924
|
// src/web/templates/pages/documents.ts
|
|
21836
21925
|
function documentsPage(data) {
|
|
21837
|
-
const label =
|
|
21926
|
+
const label = typeLabel2(data.type);
|
|
21838
21927
|
const statusOptions = data.statuses.map(
|
|
21839
21928
|
(s) => `<option value="${escapeHtml(s)}"${data.filterStatus === s ? " selected" : ""}>${escapeHtml(s)}</option>`
|
|
21840
21929
|
).join("");
|
|
@@ -21908,7 +21997,7 @@ function documentsPage(data) {
|
|
|
21908
21997
|
// src/web/templates/pages/document-detail.ts
|
|
21909
21998
|
function documentDetailPage(doc) {
|
|
21910
21999
|
const fm = doc.frontmatter;
|
|
21911
|
-
const label =
|
|
22000
|
+
const label = typeLabel2(fm.type);
|
|
21912
22001
|
const skipKeys = /* @__PURE__ */ new Set(["title", "type"]);
|
|
21913
22002
|
const entries = Object.entries(fm).filter(
|
|
21914
22003
|
([key]) => !skipKeys.has(key) && fm[key] != null
|
|
@@ -22607,7 +22696,7 @@ function poDashboardPage(ctx) {
|
|
|
22607
22696
|
<tr>
|
|
22608
22697
|
<td><a href="/docs/${d.frontmatter.type}/${escapeHtml(d.frontmatter.id)}">${escapeHtml(d.frontmatter.id)}</a></td>
|
|
22609
22698
|
<td>${escapeHtml(d.frontmatter.title)}</td>
|
|
22610
|
-
<td>${escapeHtml(
|
|
22699
|
+
<td>${escapeHtml(typeLabel2(d.frontmatter.type))}</td>
|
|
22611
22700
|
<td>${statusBadge(d.frontmatter.status)}</td>
|
|
22612
22701
|
<td>${formatDate(d.frontmatter.updated ?? d.frontmatter.created)}</td>
|
|
22613
22702
|
</tr>`).join("")}
|
|
@@ -23115,7 +23204,7 @@ function poDeliveryPage(ctx) {
|
|
|
23115
23204
|
<tr>
|
|
23116
23205
|
<td><a href="/docs/${escapeHtml(d.frontmatter.type)}/${escapeHtml(d.frontmatter.id)}">${escapeHtml(d.frontmatter.id)}</a></td>
|
|
23117
23206
|
<td>${escapeHtml(d.frontmatter.title)}</td>
|
|
23118
|
-
<td>${escapeHtml(
|
|
23207
|
+
<td>${escapeHtml(typeLabel2(d.frontmatter.type))}</td>
|
|
23119
23208
|
<td>${statusBadge(d.frontmatter.status)}</td>
|
|
23120
23209
|
<td>${formatDate(d.frontmatter.updated ?? d.frontmatter.created)}</td>
|
|
23121
23210
|
</tr>`).join("")}
|
|
@@ -23433,15 +23522,15 @@ function sprintSummaryPage(data, cached2) {
|
|
|
23433
23522
|
</div>`,
|
|
23434
23523
|
{ titleTag: "h3" }
|
|
23435
23524
|
) : "";
|
|
23436
|
-
const
|
|
23437
|
-
"
|
|
23438
|
-
"
|
|
23439
|
-
"
|
|
23440
|
-
"
|
|
23441
|
-
"
|
|
23442
|
-
"
|
|
23443
|
-
"
|
|
23444
|
-
"
|
|
23525
|
+
const FOCUS_BORDER_PALETTE = [
|
|
23526
|
+
"hsl(220, 60%, 55%)",
|
|
23527
|
+
"hsl(160, 50%, 45%)",
|
|
23528
|
+
"hsl(280, 45%, 55%)",
|
|
23529
|
+
"hsl(30, 65%, 55%)",
|
|
23530
|
+
"hsl(340, 50%, 55%)",
|
|
23531
|
+
"hsl(190, 50%, 45%)",
|
|
23532
|
+
"hsl(60, 50%, 50%)",
|
|
23533
|
+
"hsl(120, 40%, 45%)"
|
|
23445
23534
|
];
|
|
23446
23535
|
function hashString(s) {
|
|
23447
23536
|
let h = 0;
|
|
@@ -23450,68 +23539,92 @@ function sprintSummaryPage(data, cached2) {
|
|
|
23450
23539
|
}
|
|
23451
23540
|
return Math.abs(h);
|
|
23452
23541
|
}
|
|
23453
|
-
|
|
23454
|
-
|
|
23455
|
-
|
|
23456
|
-
|
|
23457
|
-
|
|
23458
|
-
|
|
23542
|
+
const focusGroups = /* @__PURE__ */ new Map();
|
|
23543
|
+
for (const item of data.workItems.items) {
|
|
23544
|
+
const focus = item.workFocus ?? "Unassigned";
|
|
23545
|
+
if (!focusGroups.has(focus)) focusGroups.set(focus, []);
|
|
23546
|
+
focusGroups.get(focus).push(item);
|
|
23547
|
+
}
|
|
23548
|
+
const focusColorMap = /* @__PURE__ */ new Map();
|
|
23549
|
+
for (const name of focusGroups.keys()) {
|
|
23550
|
+
focusColorMap.set(name, FOCUS_BORDER_PALETTE[hashString(name) % FOCUS_BORDER_PALETTE.length]);
|
|
23551
|
+
}
|
|
23552
|
+
function countFocusStats(items) {
|
|
23553
|
+
let total = 0;
|
|
23554
|
+
let done = 0;
|
|
23555
|
+
let inProgress = 0;
|
|
23556
|
+
function walk(list) {
|
|
23557
|
+
for (const w of list) {
|
|
23558
|
+
if (w.type !== "contribution") {
|
|
23559
|
+
total++;
|
|
23560
|
+
const s = w.status.toLowerCase();
|
|
23561
|
+
if (s === "done" || s === "closed" || s === "resolved" || s === "decided") done++;
|
|
23562
|
+
else if (s === "in-progress" || s === "in progress") inProgress++;
|
|
23563
|
+
}
|
|
23564
|
+
if (w.children) walk(w.children);
|
|
23459
23565
|
}
|
|
23460
23566
|
}
|
|
23461
|
-
|
|
23567
|
+
walk(items);
|
|
23568
|
+
return { total, done, inProgress };
|
|
23462
23569
|
}
|
|
23463
|
-
|
|
23464
|
-
const streamColorMap = /* @__PURE__ */ new Map();
|
|
23465
|
-
for (const name of uniqueStreams) {
|
|
23466
|
-
streamColorMap.set(name, STREAM_PALETTE[hashString(name) % STREAM_PALETTE.length]);
|
|
23467
|
-
}
|
|
23468
|
-
const streamStyleRules = [...streamColorMap.entries()].map(([name, color]) => `tr[data-stream="${escapeHtml(name)}"] td { background: ${color}; }`).join("\n");
|
|
23469
|
-
const streamStyleBlock = streamStyleRules ? `<style>${streamStyleRules}</style>` : "";
|
|
23470
|
-
function renderItemRows(items, depth = 0) {
|
|
23570
|
+
function renderItemRows(items, borderColor, depth = 0) {
|
|
23471
23571
|
return items.flatMap((w) => {
|
|
23472
23572
|
const isChild = depth > 0;
|
|
23473
23573
|
const isContribution = w.type === "contribution";
|
|
23474
|
-
const classes = [];
|
|
23574
|
+
const classes = ["focus-row"];
|
|
23475
23575
|
if (isContribution) classes.push("contribution-row");
|
|
23476
23576
|
else if (isChild) classes.push("child-row");
|
|
23477
|
-
const dataStream = w.workStream ? ` data-stream="${escapeHtml(w.workStream)}"` : "";
|
|
23478
|
-
const rowAttrs = classes.length > 0 ? ` class="${classes.join(" ")}"` : "";
|
|
23479
23577
|
const indent = depth > 0 ? ` style="padding-left: ${0.75 + depth * 1}rem"` : "";
|
|
23480
|
-
const streamCell = w.workStream ? `<span class="badge badge-subtle">${escapeHtml(w.workStream)}</span>` : "";
|
|
23481
23578
|
const progressCell = !isContribution && w.progress !== void 0 ? `<div class="mini-progress-bar"><div class="mini-progress-fill" style="width:${w.progress}%"></div><span class="mini-progress-label">${w.progress}%</span></div>` : "";
|
|
23482
23579
|
const row = `
|
|
23483
|
-
<tr${
|
|
23580
|
+
<tr class="${classes.join(" ")}" style="--focus-color: ${borderColor}">
|
|
23484
23581
|
<td${indent}><a href="/docs/${escapeHtml(w.type)}/${escapeHtml(w.id)}">${escapeHtml(w.id)}</a></td>
|
|
23485
23582
|
<td>${escapeHtml(w.title)}</td>
|
|
23486
|
-
<td>${streamCell}</td>
|
|
23487
|
-
<td>${escapeHtml(typeLabel(w.type))}</td>
|
|
23488
23583
|
<td>${statusBadge(w.status)}</td>
|
|
23489
23584
|
<td>${progressCell}</td>
|
|
23490
23585
|
</tr>`;
|
|
23491
|
-
const childRows = w.children ? renderItemRows(w.children, depth + 1) : [];
|
|
23586
|
+
const childRows = w.children ? renderItemRows(w.children, borderColor, depth + 1) : [];
|
|
23492
23587
|
return [row, ...childRows];
|
|
23493
23588
|
});
|
|
23494
23589
|
}
|
|
23495
|
-
const
|
|
23496
|
-
const
|
|
23497
|
-
|
|
23498
|
-
|
|
23499
|
-
|
|
23500
|
-
|
|
23501
|
-
|
|
23502
|
-
|
|
23590
|
+
const allWorkItemRows = [];
|
|
23591
|
+
for (const [focus, items] of focusGroups) {
|
|
23592
|
+
const color = focusColorMap.get(focus);
|
|
23593
|
+
const stats = countFocusStats(items);
|
|
23594
|
+
const pct = stats.total > 0 ? Math.round(stats.done / stats.total * 100) : 0;
|
|
23595
|
+
const summaryParts = [];
|
|
23596
|
+
if (stats.done > 0) summaryParts.push(`${stats.done} done`);
|
|
23597
|
+
if (stats.inProgress > 0) summaryParts.push(`${stats.inProgress} in progress`);
|
|
23598
|
+
const remaining = stats.total - stats.done - stats.inProgress;
|
|
23599
|
+
if (remaining > 0) summaryParts.push(`${remaining} open`);
|
|
23600
|
+
allWorkItemRows.push(`
|
|
23601
|
+
<tr class="focus-group-header" style="--focus-color: ${color}">
|
|
23602
|
+
<td colspan="2">
|
|
23603
|
+
<span class="focus-group-name">${escapeHtml(focus)}</span>
|
|
23604
|
+
<span class="focus-group-stats">${summaryParts.join(" / ")}</span>
|
|
23605
|
+
</td>
|
|
23606
|
+
<td colspan="2">
|
|
23607
|
+
<div class="mini-progress-bar focus-group-progress"><div class="mini-progress-fill" style="width:${pct}%"></div><span class="mini-progress-label">${pct}%</span></div>
|
|
23608
|
+
</td>
|
|
23609
|
+
</tr>`);
|
|
23610
|
+
allWorkItemRows.push(...renderItemRows(items, color));
|
|
23611
|
+
}
|
|
23612
|
+
const tableHeaders = `<tr>
|
|
23613
|
+
<th>ID</th>
|
|
23614
|
+
<th>Title</th>
|
|
23615
|
+
<th>Status</th>
|
|
23616
|
+
<th>Progress</th>
|
|
23503
23617
|
</tr>`;
|
|
23504
|
-
const workItemsSection =
|
|
23618
|
+
const workItemsSection = allWorkItemRows.length > 0 ? collapsibleSection(
|
|
23505
23619
|
"ss-work-items",
|
|
23506
23620
|
"Work Items",
|
|
23507
|
-
|
|
23508
|
-
<div class="table-wrap">
|
|
23621
|
+
`<div class="table-wrap">
|
|
23509
23622
|
<table id="work-items-table">
|
|
23510
23623
|
<thead>
|
|
23511
|
-
${
|
|
23624
|
+
${tableHeaders}
|
|
23512
23625
|
</thead>
|
|
23513
23626
|
<tbody>
|
|
23514
|
-
${
|
|
23627
|
+
${allWorkItemRows.join("")}
|
|
23515
23628
|
</tbody>
|
|
23516
23629
|
</table>
|
|
23517
23630
|
</div>`,
|
|
@@ -23587,61 +23700,6 @@ function sprintSummaryPage(data, cached2) {
|
|
|
23587
23700
|
</div>
|
|
23588
23701
|
|
|
23589
23702
|
<script>
|
|
23590
|
-
var _sortCol = -1;
|
|
23591
|
-
var _sortAsc = true;
|
|
23592
|
-
|
|
23593
|
-
function sortWorkItems(col) {
|
|
23594
|
-
var table = document.getElementById('work-items-table');
|
|
23595
|
-
if (!table) return;
|
|
23596
|
-
var tbody = table.querySelector('tbody');
|
|
23597
|
-
var allRows = Array.from(tbody.querySelectorAll('tr'));
|
|
23598
|
-
|
|
23599
|
-
// Toggle direction if clicking the same column
|
|
23600
|
-
if (_sortCol === col) {
|
|
23601
|
-
_sortAsc = !_sortAsc;
|
|
23602
|
-
} else {
|
|
23603
|
-
_sortCol = col;
|
|
23604
|
-
_sortAsc = true;
|
|
23605
|
-
}
|
|
23606
|
-
|
|
23607
|
-
// Update sort arrows
|
|
23608
|
-
for (var i = 0; i < 6; i++) {
|
|
23609
|
-
var arrow = document.getElementById('sort-arrow-' + i);
|
|
23610
|
-
if (arrow) arrow.textContent = i === col ? (_sortAsc ? ' \\u25B2' : ' \\u25BC') : '';
|
|
23611
|
-
}
|
|
23612
|
-
|
|
23613
|
-
// Group rows: root rows + their child/contribution rows
|
|
23614
|
-
var groups = [];
|
|
23615
|
-
var current = null;
|
|
23616
|
-
for (var r = 0; r < allRows.length; r++) {
|
|
23617
|
-
var row = allRows[r];
|
|
23618
|
-
var isChild = row.classList.contains('child-row') || row.classList.contains('contribution-row');
|
|
23619
|
-
if (!isChild) {
|
|
23620
|
-
current = { root: row, children: [] };
|
|
23621
|
-
groups.push(current);
|
|
23622
|
-
} else if (current) {
|
|
23623
|
-
current.children.push(row);
|
|
23624
|
-
}
|
|
23625
|
-
}
|
|
23626
|
-
|
|
23627
|
-
// Sort groups by root row text content of target column
|
|
23628
|
-
groups.sort(function(a, b) {
|
|
23629
|
-
var aText = (a.root.children[col] ? a.root.children[col].textContent : '').trim().toLowerCase();
|
|
23630
|
-
var bText = (b.root.children[col] ? b.root.children[col].textContent : '').trim().toLowerCase();
|
|
23631
|
-
if (aText < bText) return _sortAsc ? -1 : 1;
|
|
23632
|
-
if (aText > bText) return _sortAsc ? 1 : -1;
|
|
23633
|
-
return 0;
|
|
23634
|
-
});
|
|
23635
|
-
|
|
23636
|
-
// Re-append rows in sorted order
|
|
23637
|
-
for (var g = 0; g < groups.length; g++) {
|
|
23638
|
-
tbody.appendChild(groups[g].root);
|
|
23639
|
-
for (var c = 0; c < groups[g].children.length; c++) {
|
|
23640
|
-
tbody.appendChild(groups[g].children[c]);
|
|
23641
|
-
}
|
|
23642
|
-
}
|
|
23643
|
-
}
|
|
23644
|
-
|
|
23645
23703
|
async function generateSummary() {
|
|
23646
23704
|
var btn = document.getElementById('generate-btn');
|
|
23647
23705
|
var loading = document.getElementById('summary-loading');
|
|
@@ -23843,7 +23901,7 @@ function dmRisksPage(ctx) {
|
|
|
23843
23901
|
<tr>
|
|
23844
23902
|
<td><a href="/docs/${escapeHtml(d.frontmatter.type)}/${escapeHtml(d.frontmatter.id)}">${escapeHtml(d.frontmatter.id)}</a></td>
|
|
23845
23903
|
<td>${escapeHtml(d.frontmatter.title)}</td>
|
|
23846
|
-
<td>${escapeHtml(
|
|
23904
|
+
<td>${escapeHtml(typeLabel2(d.frontmatter.type))}</td>
|
|
23847
23905
|
<td>${d.frontmatter.owner ? escapeHtml(d.frontmatter.owner) : '<span class="text-dim">\u2014</span>'}</td>
|
|
23848
23906
|
<td>${formatDate(d.frontmatter.created)}</td>
|
|
23849
23907
|
</tr>`).join("")}
|
|
@@ -23867,7 +23925,7 @@ function dmRisksPage(ctx) {
|
|
|
23867
23925
|
<tr>
|
|
23868
23926
|
<td><a href="/docs/${escapeHtml(d.frontmatter.type)}/${escapeHtml(d.frontmatter.id)}">${escapeHtml(d.frontmatter.id)}</a></td>
|
|
23869
23927
|
<td>${escapeHtml(d.frontmatter.title)}</td>
|
|
23870
|
-
<td>${escapeHtml(
|
|
23928
|
+
<td>${escapeHtml(typeLabel2(d.frontmatter.type))}</td>
|
|
23871
23929
|
<td>${statusBadge(d.frontmatter.status)}</td>
|
|
23872
23930
|
<td>${formatDate(d.frontmatter.created)}</td>
|
|
23873
23931
|
<td><span class="${ageDays > 30 ? "priority-high" : "priority-medium"}">${ageDays}d</span></td>
|
|
@@ -24378,7 +24436,23 @@ function tlSprintPage(ctx) {
|
|
|
24378
24436
|
</div>`;
|
|
24379
24437
|
}
|
|
24380
24438
|
const techTypes = /* @__PURE__ */ new Set(["epic", "task"]);
|
|
24381
|
-
const techItems =
|
|
24439
|
+
const techItems = [];
|
|
24440
|
+
for (const item of data.workItems.items) {
|
|
24441
|
+
if (techTypes.has(item.type)) {
|
|
24442
|
+
techItems.push(item);
|
|
24443
|
+
} else if (item.children) {
|
|
24444
|
+
const promoteChildren = (children) => {
|
|
24445
|
+
for (const child of children) {
|
|
24446
|
+
if (techTypes.has(child.type)) {
|
|
24447
|
+
techItems.push(child);
|
|
24448
|
+
} else if (child.children) {
|
|
24449
|
+
promoteChildren(child.children);
|
|
24450
|
+
}
|
|
24451
|
+
}
|
|
24452
|
+
};
|
|
24453
|
+
promoteChildren(item.children);
|
|
24454
|
+
}
|
|
24455
|
+
}
|
|
24382
24456
|
const techDone = techItems.filter((w) => DONE_STATUSES11.has(w.status)).length;
|
|
24383
24457
|
const allDocs = ctx.store.list();
|
|
24384
24458
|
const tlContributions = allDocs.filter((d) => TL_CONTRIBUTION_TYPES.has(d.frontmatter.type));
|
|
@@ -24416,16 +24490,16 @@ function tlSprintPage(ctx) {
|
|
|
24416
24490
|
`<div class="table-wrap">
|
|
24417
24491
|
<table>
|
|
24418
24492
|
<thead>
|
|
24419
|
-
<tr><th>ID</th><th>Title</th><th>Type</th><th>Status</th><th>
|
|
24493
|
+
<tr><th>ID</th><th>Title</th><th>Type</th><th>Status</th><th>Focus</th></tr>
|
|
24420
24494
|
</thead>
|
|
24421
24495
|
<tbody>
|
|
24422
24496
|
${techItems.map((w) => `
|
|
24423
24497
|
<tr>
|
|
24424
24498
|
<td><a href="/docs/${escapeHtml(w.type)}/${escapeHtml(w.id)}">${escapeHtml(w.id)}</a></td>
|
|
24425
24499
|
<td>${escapeHtml(w.title)}</td>
|
|
24426
|
-
<td>${escapeHtml(
|
|
24500
|
+
<td>${escapeHtml(typeLabel2(w.type))}</td>
|
|
24427
24501
|
<td>${statusBadge(w.status)}</td>
|
|
24428
|
-
<td>${w.
|
|
24502
|
+
<td>${w.workFocus ? `<span class="badge badge-subtle">${escapeHtml(w.workFocus)}</span>` : '<span class="text-dim">\u2014</span>'}</td>
|
|
24429
24503
|
</tr>`).join("")}
|
|
24430
24504
|
</tbody>
|
|
24431
24505
|
</table>
|
|
@@ -24445,7 +24519,7 @@ function tlSprintPage(ctx) {
|
|
|
24445
24519
|
<tr>
|
|
24446
24520
|
<td><a href="/docs/${escapeHtml(d.frontmatter.type)}/${escapeHtml(d.frontmatter.id)}">${escapeHtml(d.frontmatter.id)}</a></td>
|
|
24447
24521
|
<td>${escapeHtml(d.frontmatter.title)}</td>
|
|
24448
|
-
<td>${escapeHtml(
|
|
24522
|
+
<td>${escapeHtml(typeLabel2(d.frontmatter.type))}</td>
|
|
24449
24523
|
<td>${statusBadge(d.frontmatter.status)}</td>
|
|
24450
24524
|
<td>${formatDate(d.frontmatter.updated ?? d.frontmatter.created)}</td>
|
|
24451
24525
|
</tr>`).join("")}
|
|
@@ -24634,7 +24708,7 @@ function timelinePage(diagrams) {
|
|
|
24634
24708
|
// src/web/templates/pages/board.ts
|
|
24635
24709
|
function boardPage(data, basePath = "/board") {
|
|
24636
24710
|
const typeOptions = data.types.map(
|
|
24637
|
-
(t) => `<option value="${escapeHtml(t)}"${data.type === t ? " selected" : ""}>${escapeHtml(
|
|
24711
|
+
(t) => `<option value="${escapeHtml(t)}"${data.type === t ? " selected" : ""}>${escapeHtml(typeLabel2(t))}s</option>`
|
|
24638
24712
|
).join("");
|
|
24639
24713
|
const columns = data.columns.map(
|
|
24640
24714
|
(col) => `
|
|
@@ -24791,7 +24865,7 @@ function upcomingPage(data) {
|
|
|
24791
24865
|
<td><span class="trending-rank">${i + 1}</span></td>
|
|
24792
24866
|
<td><a href="/docs/${escapeHtml(t.type)}/${escapeHtml(t.id)}">${escapeHtml(t.id)}</a></td>
|
|
24793
24867
|
<td>${escapeHtml(t.title)}</td>
|
|
24794
|
-
<td>${escapeHtml(
|
|
24868
|
+
<td>${escapeHtml(typeLabel2(t.type))}</td>
|
|
24795
24869
|
<td>${statusBadge(t.status)}</td>
|
|
24796
24870
|
<td><span class="trending-score">${t.score}</span></td>
|
|
24797
24871
|
<td>${t.signals.map((s) => `<span class="signal-tag">${escapeHtml(s.factor)} +${s.points}</span>`).join(" ")}</td>
|
|
@@ -24885,7 +24959,7 @@ function buildPersonaLayoutOpts(persona, activePath, navGroups) {
|
|
|
24885
24959
|
const artifactGroupsHtml = navGroups.map((group) => {
|
|
24886
24960
|
const links = group.types.map((type) => {
|
|
24887
24961
|
const href = `/docs/${type}?persona=${persona}`;
|
|
24888
|
-
return `<a href="${href}" class="${isActive(`/docs/${type}`)}">${
|
|
24962
|
+
return `<a href="${href}" class="${isActive(`/docs/${type}`)}">${typeLabel2(type)}s</a>`;
|
|
24889
24963
|
}).join("\n ");
|
|
24890
24964
|
const groupActive = group.types.some(
|
|
24891
24965
|
(type) => isActive(`/docs/${type}`) !== ""
|
|
@@ -25311,6 +25385,556 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
25311
25385
|
];
|
|
25312
25386
|
}
|
|
25313
25387
|
|
|
25388
|
+
// src/agent/tools/doctor.ts
|
|
25389
|
+
import { tool as tool23 } from "@anthropic-ai/claude-agent-sdk";
|
|
25390
|
+
|
|
25391
|
+
// src/doctor/rules/tag-migration.ts
|
|
25392
|
+
var RULE_ID = "tag-migration";
|
|
25393
|
+
var RULE_NAME = "Tag Migration";
|
|
25394
|
+
var tagMigrationRule = {
|
|
25395
|
+
id: RULE_ID,
|
|
25396
|
+
name: RULE_NAME,
|
|
25397
|
+
description: "Detects deprecated stream:* tags and replaces them with focus:*",
|
|
25398
|
+
scan(ctx) {
|
|
25399
|
+
const issues = [];
|
|
25400
|
+
for (const doc of ctx.allDocuments) {
|
|
25401
|
+
const tags = doc.frontmatter.tags;
|
|
25402
|
+
if (!Array.isArray(tags)) continue;
|
|
25403
|
+
const streamTags = tags.filter((t) => t.startsWith("stream:"));
|
|
25404
|
+
for (const tag of streamTags) {
|
|
25405
|
+
issues.push({
|
|
25406
|
+
ruleId: RULE_ID,
|
|
25407
|
+
ruleName: RULE_NAME,
|
|
25408
|
+
documentId: doc.frontmatter.id,
|
|
25409
|
+
filePath: doc.filePath,
|
|
25410
|
+
documentType: doc.frontmatter.type,
|
|
25411
|
+
message: `Deprecated tag "${tag}" should be "${tag.replace("stream:", "focus:")}"`,
|
|
25412
|
+
severity: "warning",
|
|
25413
|
+
fixable: true
|
|
25414
|
+
});
|
|
25415
|
+
}
|
|
25416
|
+
}
|
|
25417
|
+
return issues;
|
|
25418
|
+
},
|
|
25419
|
+
fix(ctx) {
|
|
25420
|
+
const fixes = [];
|
|
25421
|
+
for (const doc of ctx.allDocuments) {
|
|
25422
|
+
const tags = doc.frontmatter.tags;
|
|
25423
|
+
if (!Array.isArray(tags)) continue;
|
|
25424
|
+
const streamTags = tags.filter((t) => t.startsWith("stream:"));
|
|
25425
|
+
if (streamTags.length === 0) continue;
|
|
25426
|
+
const newTags = tags.map(
|
|
25427
|
+
(t) => t.startsWith("stream:") ? t.replace("stream:", "focus:") : t
|
|
25428
|
+
);
|
|
25429
|
+
ctx.store.update(doc.frontmatter.id, { tags: newTags });
|
|
25430
|
+
for (const tag of streamTags) {
|
|
25431
|
+
fixes.push({
|
|
25432
|
+
issue: {
|
|
25433
|
+
ruleId: RULE_ID,
|
|
25434
|
+
ruleName: RULE_NAME,
|
|
25435
|
+
documentId: doc.frontmatter.id,
|
|
25436
|
+
filePath: doc.filePath,
|
|
25437
|
+
documentType: doc.frontmatter.type,
|
|
25438
|
+
message: `Deprecated tag "${tag}" should be "${tag.replace("stream:", "focus:")}"`,
|
|
25439
|
+
severity: "warning",
|
|
25440
|
+
fixable: true
|
|
25441
|
+
},
|
|
25442
|
+
fixDescription: `Renamed "${tag}" to "${tag.replace("stream:", "focus:")}"`
|
|
25443
|
+
});
|
|
25444
|
+
}
|
|
25445
|
+
}
|
|
25446
|
+
return fixes;
|
|
25447
|
+
}
|
|
25448
|
+
};
|
|
25449
|
+
|
|
25450
|
+
// src/doctor/rules/array-normalization.ts
|
|
25451
|
+
var RULE_ID2 = "array-normalization";
|
|
25452
|
+
var RULE_NAME2 = "Array Normalization";
|
|
25453
|
+
var FIELDS = [
|
|
25454
|
+
{
|
|
25455
|
+
field: "linkedEpic",
|
|
25456
|
+
aliases: ["linkedEpics"],
|
|
25457
|
+
normalize: normalizeLinkedEpics
|
|
25458
|
+
},
|
|
25459
|
+
{
|
|
25460
|
+
field: "linkedFeature",
|
|
25461
|
+
aliases: ["linkedFeatures"],
|
|
25462
|
+
normalize: normalizeLinkedFeatures
|
|
25463
|
+
}
|
|
25464
|
+
];
|
|
25465
|
+
var arrayNormalizationRule = {
|
|
25466
|
+
id: RULE_ID2,
|
|
25467
|
+
name: RULE_NAME2,
|
|
25468
|
+
description: "Normalizes linkedEpic/linkedFeature from strings to arrays and resolves field aliases",
|
|
25469
|
+
scan(ctx) {
|
|
25470
|
+
const issues = [];
|
|
25471
|
+
for (const doc of ctx.allDocuments) {
|
|
25472
|
+
const fm = doc.frontmatter;
|
|
25473
|
+
for (const cfg of FIELDS) {
|
|
25474
|
+
for (const alias of cfg.aliases) {
|
|
25475
|
+
if (fm[alias] !== void 0) {
|
|
25476
|
+
issues.push({
|
|
25477
|
+
ruleId: RULE_ID2,
|
|
25478
|
+
ruleName: RULE_NAME2,
|
|
25479
|
+
documentId: doc.frontmatter.id,
|
|
25480
|
+
filePath: doc.filePath,
|
|
25481
|
+
documentType: doc.frontmatter.type,
|
|
25482
|
+
message: `Field "${alias}" should be renamed to "${cfg.field}"`,
|
|
25483
|
+
severity: "warning",
|
|
25484
|
+
fixable: true
|
|
25485
|
+
});
|
|
25486
|
+
}
|
|
25487
|
+
}
|
|
25488
|
+
const value = fm[cfg.field];
|
|
25489
|
+
if (typeof value === "string") {
|
|
25490
|
+
issues.push({
|
|
25491
|
+
ruleId: RULE_ID2,
|
|
25492
|
+
ruleName: RULE_NAME2,
|
|
25493
|
+
documentId: doc.frontmatter.id,
|
|
25494
|
+
filePath: doc.filePath,
|
|
25495
|
+
documentType: doc.frontmatter.type,
|
|
25496
|
+
message: `Field "${cfg.field}" is a string ("${value}") but should be an array`,
|
|
25497
|
+
severity: "warning",
|
|
25498
|
+
fixable: true
|
|
25499
|
+
});
|
|
25500
|
+
}
|
|
25501
|
+
}
|
|
25502
|
+
}
|
|
25503
|
+
return issues;
|
|
25504
|
+
},
|
|
25505
|
+
fix(ctx) {
|
|
25506
|
+
const fixes = [];
|
|
25507
|
+
for (const doc of ctx.allDocuments) {
|
|
25508
|
+
const fm = doc.frontmatter;
|
|
25509
|
+
const updates = {};
|
|
25510
|
+
let needsUpdate = false;
|
|
25511
|
+
for (const cfg of FIELDS) {
|
|
25512
|
+
for (const alias of cfg.aliases) {
|
|
25513
|
+
if (fm[alias] !== void 0) {
|
|
25514
|
+
const aliasValues = cfg.normalize(fm[alias]);
|
|
25515
|
+
const existing = cfg.normalize(fm[cfg.field]);
|
|
25516
|
+
const merged = [.../* @__PURE__ */ new Set([...existing, ...aliasValues])];
|
|
25517
|
+
updates[cfg.field] = merged;
|
|
25518
|
+
updates[alias] = void 0;
|
|
25519
|
+
needsUpdate = true;
|
|
25520
|
+
fixes.push({
|
|
25521
|
+
issue: {
|
|
25522
|
+
ruleId: RULE_ID2,
|
|
25523
|
+
ruleName: RULE_NAME2,
|
|
25524
|
+
documentId: doc.frontmatter.id,
|
|
25525
|
+
filePath: doc.filePath,
|
|
25526
|
+
documentType: doc.frontmatter.type,
|
|
25527
|
+
message: `Field "${alias}" should be renamed to "${cfg.field}"`,
|
|
25528
|
+
severity: "warning",
|
|
25529
|
+
fixable: true
|
|
25530
|
+
},
|
|
25531
|
+
fixDescription: `Merged "${alias}" into "${cfg.field}" and removed alias`
|
|
25532
|
+
});
|
|
25533
|
+
}
|
|
25534
|
+
}
|
|
25535
|
+
const value = updates[cfg.field] ?? fm[cfg.field];
|
|
25536
|
+
if (typeof value === "string") {
|
|
25537
|
+
updates[cfg.field] = cfg.normalize(value);
|
|
25538
|
+
needsUpdate = true;
|
|
25539
|
+
fixes.push({
|
|
25540
|
+
issue: {
|
|
25541
|
+
ruleId: RULE_ID2,
|
|
25542
|
+
ruleName: RULE_NAME2,
|
|
25543
|
+
documentId: doc.frontmatter.id,
|
|
25544
|
+
filePath: doc.filePath,
|
|
25545
|
+
documentType: doc.frontmatter.type,
|
|
25546
|
+
message: `Field "${cfg.field}" is a string ("${value}") but should be an array`,
|
|
25547
|
+
severity: "warning",
|
|
25548
|
+
fixable: true
|
|
25549
|
+
},
|
|
25550
|
+
fixDescription: `Normalized "${cfg.field}" from string to array`
|
|
25551
|
+
});
|
|
25552
|
+
}
|
|
25553
|
+
}
|
|
25554
|
+
if (needsUpdate) {
|
|
25555
|
+
ctx.store.update(doc.frontmatter.id, updates);
|
|
25556
|
+
}
|
|
25557
|
+
}
|
|
25558
|
+
return fixes;
|
|
25559
|
+
}
|
|
25560
|
+
};
|
|
25561
|
+
|
|
25562
|
+
// src/doctor/rules/missing-auto-tags.ts
|
|
25563
|
+
var RULE_ID3 = "missing-auto-tags";
|
|
25564
|
+
var RULE_NAME3 = "Missing Auto Tags";
|
|
25565
|
+
var missingAutoTagsRule = {
|
|
25566
|
+
id: RULE_ID3,
|
|
25567
|
+
name: RULE_NAME3,
|
|
25568
|
+
description: "Ensures tasks have epic:E-xxx tags for their linkedEpic and epics have feature:F-xxx tags",
|
|
25569
|
+
scan(ctx) {
|
|
25570
|
+
const issues = [];
|
|
25571
|
+
for (const doc of ctx.allDocuments) {
|
|
25572
|
+
const fm = doc.frontmatter;
|
|
25573
|
+
const tags = doc.frontmatter.tags ?? [];
|
|
25574
|
+
const linkedEpics = normalizeLinkedEpics(fm.linkedEpic);
|
|
25575
|
+
if (linkedEpics.length > 0) {
|
|
25576
|
+
const expected = generateEpicTags(linkedEpics);
|
|
25577
|
+
const missing = expected.filter((t) => !tags.includes(t));
|
|
25578
|
+
for (const tag of missing) {
|
|
25579
|
+
issues.push({
|
|
25580
|
+
ruleId: RULE_ID3,
|
|
25581
|
+
ruleName: RULE_NAME3,
|
|
25582
|
+
documentId: doc.frontmatter.id,
|
|
25583
|
+
filePath: doc.filePath,
|
|
25584
|
+
documentType: doc.frontmatter.type,
|
|
25585
|
+
message: `Missing auto-tag "${tag}" for linkedEpic`,
|
|
25586
|
+
severity: "warning",
|
|
25587
|
+
fixable: true
|
|
25588
|
+
});
|
|
25589
|
+
}
|
|
25590
|
+
}
|
|
25591
|
+
const linkedFeatures = normalizeLinkedFeatures(fm.linkedFeature);
|
|
25592
|
+
if (linkedFeatures.length > 0) {
|
|
25593
|
+
const expected = generateFeatureTags(linkedFeatures);
|
|
25594
|
+
const missing = expected.filter((t) => !tags.includes(t));
|
|
25595
|
+
for (const tag of missing) {
|
|
25596
|
+
issues.push({
|
|
25597
|
+
ruleId: RULE_ID3,
|
|
25598
|
+
ruleName: RULE_NAME3,
|
|
25599
|
+
documentId: doc.frontmatter.id,
|
|
25600
|
+
filePath: doc.filePath,
|
|
25601
|
+
documentType: doc.frontmatter.type,
|
|
25602
|
+
message: `Missing auto-tag "${tag}" for linkedFeature`,
|
|
25603
|
+
severity: "warning",
|
|
25604
|
+
fixable: true
|
|
25605
|
+
});
|
|
25606
|
+
}
|
|
25607
|
+
}
|
|
25608
|
+
}
|
|
25609
|
+
return issues;
|
|
25610
|
+
},
|
|
25611
|
+
fix(ctx) {
|
|
25612
|
+
const fixes = [];
|
|
25613
|
+
for (const doc of ctx.allDocuments) {
|
|
25614
|
+
const fm = doc.frontmatter;
|
|
25615
|
+
const tags = [...doc.frontmatter.tags ?? []];
|
|
25616
|
+
let changed = false;
|
|
25617
|
+
const linkedEpics = normalizeLinkedEpics(fm.linkedEpic);
|
|
25618
|
+
if (linkedEpics.length > 0) {
|
|
25619
|
+
const expected = generateEpicTags(linkedEpics);
|
|
25620
|
+
for (const tag of expected) {
|
|
25621
|
+
if (!tags.includes(tag)) {
|
|
25622
|
+
tags.push(tag);
|
|
25623
|
+
changed = true;
|
|
25624
|
+
fixes.push({
|
|
25625
|
+
issue: {
|
|
25626
|
+
ruleId: RULE_ID3,
|
|
25627
|
+
ruleName: RULE_NAME3,
|
|
25628
|
+
documentId: doc.frontmatter.id,
|
|
25629
|
+
filePath: doc.filePath,
|
|
25630
|
+
documentType: doc.frontmatter.type,
|
|
25631
|
+
message: `Missing auto-tag "${tag}" for linkedEpic`,
|
|
25632
|
+
severity: "warning",
|
|
25633
|
+
fixable: true
|
|
25634
|
+
},
|
|
25635
|
+
fixDescription: `Added tag "${tag}"`
|
|
25636
|
+
});
|
|
25637
|
+
}
|
|
25638
|
+
}
|
|
25639
|
+
}
|
|
25640
|
+
const linkedFeatures = normalizeLinkedFeatures(fm.linkedFeature);
|
|
25641
|
+
if (linkedFeatures.length > 0) {
|
|
25642
|
+
const expected = generateFeatureTags(linkedFeatures);
|
|
25643
|
+
for (const tag of expected) {
|
|
25644
|
+
if (!tags.includes(tag)) {
|
|
25645
|
+
tags.push(tag);
|
|
25646
|
+
changed = true;
|
|
25647
|
+
fixes.push({
|
|
25648
|
+
issue: {
|
|
25649
|
+
ruleId: RULE_ID3,
|
|
25650
|
+
ruleName: RULE_NAME3,
|
|
25651
|
+
documentId: doc.frontmatter.id,
|
|
25652
|
+
filePath: doc.filePath,
|
|
25653
|
+
documentType: doc.frontmatter.type,
|
|
25654
|
+
message: `Missing auto-tag "${tag}" for linkedFeature`,
|
|
25655
|
+
severity: "warning",
|
|
25656
|
+
fixable: true
|
|
25657
|
+
},
|
|
25658
|
+
fixDescription: `Added tag "${tag}"`
|
|
25659
|
+
});
|
|
25660
|
+
}
|
|
25661
|
+
}
|
|
25662
|
+
}
|
|
25663
|
+
if (changed) {
|
|
25664
|
+
ctx.store.update(doc.frontmatter.id, { tags });
|
|
25665
|
+
}
|
|
25666
|
+
}
|
|
25667
|
+
return fixes;
|
|
25668
|
+
}
|
|
25669
|
+
};
|
|
25670
|
+
|
|
25671
|
+
// src/doctor/rules/progress-consistency.ts
|
|
25672
|
+
var RULE_ID4 = "progress-consistency";
|
|
25673
|
+
var RULE_NAME4 = "Progress Consistency";
|
|
25674
|
+
var progressConsistencyRule = {
|
|
25675
|
+
id: RULE_ID4,
|
|
25676
|
+
name: RULE_NAME4,
|
|
25677
|
+
description: "Detects done-status documents with progress != 100 and progressOverride:true without a progress value",
|
|
25678
|
+
scan(ctx) {
|
|
25679
|
+
const issues = [];
|
|
25680
|
+
for (const doc of ctx.allDocuments) {
|
|
25681
|
+
const fm = doc.frontmatter;
|
|
25682
|
+
const status = doc.frontmatter.status;
|
|
25683
|
+
const progress = fm.progress;
|
|
25684
|
+
const progressOverride = fm.progressOverride;
|
|
25685
|
+
if (status === "done" && progress !== void 0 && progress !== 100) {
|
|
25686
|
+
issues.push({
|
|
25687
|
+
ruleId: RULE_ID4,
|
|
25688
|
+
ruleName: RULE_NAME4,
|
|
25689
|
+
documentId: doc.frontmatter.id,
|
|
25690
|
+
filePath: doc.filePath,
|
|
25691
|
+
documentType: doc.frontmatter.type,
|
|
25692
|
+
message: `Status is "done" but progress is ${progress} (expected 100)`,
|
|
25693
|
+
severity: "error",
|
|
25694
|
+
fixable: true
|
|
25695
|
+
});
|
|
25696
|
+
}
|
|
25697
|
+
if (progressOverride === true && progress === void 0) {
|
|
25698
|
+
issues.push({
|
|
25699
|
+
ruleId: RULE_ID4,
|
|
25700
|
+
ruleName: RULE_NAME4,
|
|
25701
|
+
documentId: doc.frontmatter.id,
|
|
25702
|
+
filePath: doc.filePath,
|
|
25703
|
+
documentType: doc.frontmatter.type,
|
|
25704
|
+
message: `progressOverride is true but no progress value is set`,
|
|
25705
|
+
severity: "warning",
|
|
25706
|
+
fixable: true
|
|
25707
|
+
});
|
|
25708
|
+
}
|
|
25709
|
+
}
|
|
25710
|
+
return issues;
|
|
25711
|
+
},
|
|
25712
|
+
fix(ctx) {
|
|
25713
|
+
const fixes = [];
|
|
25714
|
+
for (const doc of ctx.allDocuments) {
|
|
25715
|
+
const fm = doc.frontmatter;
|
|
25716
|
+
const status = doc.frontmatter.status;
|
|
25717
|
+
const progress = fm.progress;
|
|
25718
|
+
const progressOverride = fm.progressOverride;
|
|
25719
|
+
if (status === "done" && progress !== void 0 && progress !== 100) {
|
|
25720
|
+
ctx.store.update(doc.frontmatter.id, { progress: 100 });
|
|
25721
|
+
fixes.push({
|
|
25722
|
+
issue: {
|
|
25723
|
+
ruleId: RULE_ID4,
|
|
25724
|
+
ruleName: RULE_NAME4,
|
|
25725
|
+
documentId: doc.frontmatter.id,
|
|
25726
|
+
filePath: doc.filePath,
|
|
25727
|
+
documentType: doc.frontmatter.type,
|
|
25728
|
+
message: `Status is "done" but progress is ${progress} (expected 100)`,
|
|
25729
|
+
severity: "error",
|
|
25730
|
+
fixable: true
|
|
25731
|
+
},
|
|
25732
|
+
fixDescription: `Set progress to 100`
|
|
25733
|
+
});
|
|
25734
|
+
}
|
|
25735
|
+
if (progressOverride === true && progress === void 0) {
|
|
25736
|
+
ctx.store.update(doc.frontmatter.id, { progressOverride: false });
|
|
25737
|
+
fixes.push({
|
|
25738
|
+
issue: {
|
|
25739
|
+
ruleId: RULE_ID4,
|
|
25740
|
+
ruleName: RULE_NAME4,
|
|
25741
|
+
documentId: doc.frontmatter.id,
|
|
25742
|
+
filePath: doc.filePath,
|
|
25743
|
+
documentType: doc.frontmatter.type,
|
|
25744
|
+
message: `progressOverride is true but no progress value is set`,
|
|
25745
|
+
severity: "warning",
|
|
25746
|
+
fixable: true
|
|
25747
|
+
},
|
|
25748
|
+
fixDescription: `Set progressOverride to false`
|
|
25749
|
+
});
|
|
25750
|
+
}
|
|
25751
|
+
}
|
|
25752
|
+
return fixes;
|
|
25753
|
+
}
|
|
25754
|
+
};
|
|
25755
|
+
|
|
25756
|
+
// src/doctor/rules/orphaned-references.ts
|
|
25757
|
+
var RULE_ID5 = "orphaned-references";
|
|
25758
|
+
var RULE_NAME5 = "Orphaned References";
|
|
25759
|
+
var REFERENCE_FIELDS = ["aboutArtifact", "linkedEpic", "linkedFeature"];
|
|
25760
|
+
var orphanedReferencesRule = {
|
|
25761
|
+
id: RULE_ID5,
|
|
25762
|
+
name: RULE_NAME5,
|
|
25763
|
+
description: "Detects references (aboutArtifact, linkedEpic, linkedFeature) pointing to non-existent documents",
|
|
25764
|
+
scan(ctx) {
|
|
25765
|
+
const issues = [];
|
|
25766
|
+
for (const doc of ctx.allDocuments) {
|
|
25767
|
+
const fm = doc.frontmatter;
|
|
25768
|
+
for (const field of REFERENCE_FIELDS) {
|
|
25769
|
+
const value = fm[field];
|
|
25770
|
+
if (value === void 0 || value === null) continue;
|
|
25771
|
+
const refs = Array.isArray(value) ? value.filter((v) => typeof v === "string") : typeof value === "string" ? [value] : [];
|
|
25772
|
+
for (const ref of refs) {
|
|
25773
|
+
if (!ctx.documentIndex.has(ref)) {
|
|
25774
|
+
issues.push({
|
|
25775
|
+
ruleId: RULE_ID5,
|
|
25776
|
+
ruleName: RULE_NAME5,
|
|
25777
|
+
documentId: doc.frontmatter.id,
|
|
25778
|
+
filePath: doc.filePath,
|
|
25779
|
+
documentType: doc.frontmatter.type,
|
|
25780
|
+
message: `Field "${field}" references "${ref}" which does not exist`,
|
|
25781
|
+
severity: "warning",
|
|
25782
|
+
fixable: false
|
|
25783
|
+
});
|
|
25784
|
+
}
|
|
25785
|
+
}
|
|
25786
|
+
}
|
|
25787
|
+
}
|
|
25788
|
+
return issues;
|
|
25789
|
+
},
|
|
25790
|
+
fix() {
|
|
25791
|
+
return [];
|
|
25792
|
+
}
|
|
25793
|
+
};
|
|
25794
|
+
|
|
25795
|
+
// src/doctor/rules/owner-role.ts
|
|
25796
|
+
var RULE_ID6 = "owner-role";
|
|
25797
|
+
var RULE_NAME6 = "Owner Role";
|
|
25798
|
+
var ownerRoleRule = {
|
|
25799
|
+
id: RULE_ID6,
|
|
25800
|
+
name: RULE_NAME6,
|
|
25801
|
+
description: `Detects owner values that are not valid persona roles (${OWNER_SHORT.join(", ")})`,
|
|
25802
|
+
scan(ctx) {
|
|
25803
|
+
const issues = [];
|
|
25804
|
+
for (const doc of ctx.allDocuments) {
|
|
25805
|
+
const owner = doc.frontmatter.owner;
|
|
25806
|
+
if (owner === void 0 || owner === null || owner === "") continue;
|
|
25807
|
+
if (!isValidOwner(owner)) {
|
|
25808
|
+
issues.push({
|
|
25809
|
+
ruleId: RULE_ID6,
|
|
25810
|
+
ruleName: RULE_NAME6,
|
|
25811
|
+
documentId: doc.frontmatter.id,
|
|
25812
|
+
filePath: doc.filePath,
|
|
25813
|
+
documentType: doc.frontmatter.type,
|
|
25814
|
+
message: `Owner "${owner}" is not a valid persona role. Expected one of: ${OWNER_SHORT.join(", ")}`,
|
|
25815
|
+
severity: "warning",
|
|
25816
|
+
fixable: false
|
|
25817
|
+
});
|
|
25818
|
+
}
|
|
25819
|
+
}
|
|
25820
|
+
return issues;
|
|
25821
|
+
},
|
|
25822
|
+
fix() {
|
|
25823
|
+
return [];
|
|
25824
|
+
}
|
|
25825
|
+
};
|
|
25826
|
+
|
|
25827
|
+
// src/doctor/rules/index.ts
|
|
25828
|
+
var allRules = [
|
|
25829
|
+
tagMigrationRule,
|
|
25830
|
+
arrayNormalizationRule,
|
|
25831
|
+
missingAutoTagsRule,
|
|
25832
|
+
progressConsistencyRule,
|
|
25833
|
+
orphanedReferencesRule,
|
|
25834
|
+
ownerRoleRule
|
|
25835
|
+
];
|
|
25836
|
+
|
|
25837
|
+
// src/doctor/engine.ts
|
|
25838
|
+
function buildDoctorContext(store) {
|
|
25839
|
+
const allDocuments = store.list();
|
|
25840
|
+
const documentIndex = new Map(
|
|
25841
|
+
allDocuments.map((doc) => [doc.frontmatter.id, doc])
|
|
25842
|
+
);
|
|
25843
|
+
return { store, allDocuments, documentIndex };
|
|
25844
|
+
}
|
|
25845
|
+
function runDoctorScan(store, ruleFilter) {
|
|
25846
|
+
const rules = resolveRules(ruleFilter);
|
|
25847
|
+
const ctx = buildDoctorContext(store);
|
|
25848
|
+
const issues = rules.flatMap((rule) => rule.scan(ctx));
|
|
25849
|
+
return buildReport(ctx, issues, []);
|
|
25850
|
+
}
|
|
25851
|
+
function runDoctorFix(store, ruleFilter) {
|
|
25852
|
+
const rules = resolveRules(ruleFilter);
|
|
25853
|
+
let ctx = buildDoctorContext(store);
|
|
25854
|
+
const allIssues = rules.flatMap((rule) => rule.scan(ctx));
|
|
25855
|
+
const allFixes = [];
|
|
25856
|
+
for (const rule of rules) {
|
|
25857
|
+
const fixes = rule.fix(ctx);
|
|
25858
|
+
allFixes.push(...fixes);
|
|
25859
|
+
if (fixes.length > 0) {
|
|
25860
|
+
ctx = buildDoctorContext(store);
|
|
25861
|
+
}
|
|
25862
|
+
}
|
|
25863
|
+
return buildReport(ctx, allIssues, allFixes);
|
|
25864
|
+
}
|
|
25865
|
+
function resolveRules(ruleFilter) {
|
|
25866
|
+
if (!ruleFilter) return allRules;
|
|
25867
|
+
const rule = allRules.find((r) => r.id === ruleFilter);
|
|
25868
|
+
if (!rule) {
|
|
25869
|
+
throw new Error(
|
|
25870
|
+
`Unknown rule: ${ruleFilter}. Available: ${allRules.map((r) => r.id).join(", ")}`
|
|
25871
|
+
);
|
|
25872
|
+
}
|
|
25873
|
+
return [rule];
|
|
25874
|
+
}
|
|
25875
|
+
function buildReport(ctx, issues, fixes) {
|
|
25876
|
+
const byRule = {};
|
|
25877
|
+
const bySeverity = { error: 0, warning: 0, info: 0 };
|
|
25878
|
+
let fixableIssues = 0;
|
|
25879
|
+
for (const issue2 of issues) {
|
|
25880
|
+
byRule[issue2.ruleId] = (byRule[issue2.ruleId] ?? 0) + 1;
|
|
25881
|
+
bySeverity[issue2.severity] = (bySeverity[issue2.severity] ?? 0) + 1;
|
|
25882
|
+
if (issue2.fixable) fixableIssues++;
|
|
25883
|
+
}
|
|
25884
|
+
return {
|
|
25885
|
+
scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25886
|
+
totalDocuments: ctx.allDocuments.length,
|
|
25887
|
+
issues,
|
|
25888
|
+
fixes,
|
|
25889
|
+
summary: {
|
|
25890
|
+
totalIssues: issues.length,
|
|
25891
|
+
fixableIssues,
|
|
25892
|
+
fixedIssues: fixes.length,
|
|
25893
|
+
byRule,
|
|
25894
|
+
bySeverity
|
|
25895
|
+
}
|
|
25896
|
+
};
|
|
25897
|
+
}
|
|
25898
|
+
|
|
25899
|
+
// src/agent/tools/doctor.ts
|
|
25900
|
+
function createDoctorTools(store) {
|
|
25901
|
+
return [
|
|
25902
|
+
tool23(
|
|
25903
|
+
"run_doctor",
|
|
25904
|
+
"Scan project documents for structural issues and optionally auto-repair them. Returns a JSON report with all issues found and fixes applied.",
|
|
25905
|
+
{
|
|
25906
|
+
fix: external_exports.boolean().optional().default(false).describe("When true, auto-repair fixable issues"),
|
|
25907
|
+
rule: external_exports.string().optional().describe(
|
|
25908
|
+
"Run only a specific rule (e.g. tag-migration, array-normalization, missing-auto-tags, progress-consistency, orphaned-references)"
|
|
25909
|
+
)
|
|
25910
|
+
},
|
|
25911
|
+
async (args) => {
|
|
25912
|
+
try {
|
|
25913
|
+
const report = args.fix ? runDoctorFix(store, args.rule) : runDoctorScan(store, args.rule);
|
|
25914
|
+
return {
|
|
25915
|
+
content: [
|
|
25916
|
+
{
|
|
25917
|
+
type: "text",
|
|
25918
|
+
text: JSON.stringify(report, null, 2)
|
|
25919
|
+
}
|
|
25920
|
+
]
|
|
25921
|
+
};
|
|
25922
|
+
} catch (err) {
|
|
25923
|
+
return {
|
|
25924
|
+
content: [
|
|
25925
|
+
{
|
|
25926
|
+
type: "text",
|
|
25927
|
+
text: `Doctor error: ${err instanceof Error ? err.message : String(err)}`
|
|
25928
|
+
}
|
|
25929
|
+
],
|
|
25930
|
+
isError: true
|
|
25931
|
+
};
|
|
25932
|
+
}
|
|
25933
|
+
}
|
|
25934
|
+
)
|
|
25935
|
+
];
|
|
25936
|
+
}
|
|
25937
|
+
|
|
25314
25938
|
// src/agent/mcp-server.ts
|
|
25315
25939
|
function createMarvinMcpServer(store, options) {
|
|
25316
25940
|
const tools = [
|
|
@@ -25322,7 +25946,8 @@ function createMarvinMcpServer(store, options) {
|
|
|
25322
25946
|
...options?.sessionStore ? createSessionTools(options.sessionStore) : [],
|
|
25323
25947
|
...options?.pluginTools ?? [],
|
|
25324
25948
|
...options?.skillTools ?? [],
|
|
25325
|
-
...options?.projectName && options?.navGroups ? createWebTools(store, options.projectName, options.navGroups) : []
|
|
25949
|
+
...options?.projectName && options?.navGroups ? createWebTools(store, options.projectName, options.navGroups) : [],
|
|
25950
|
+
...createDoctorTools(store)
|
|
25326
25951
|
];
|
|
25327
25952
|
return createSdkMcpServer({
|
|
25328
25953
|
name: "marvin-governance",
|
|
@@ -25394,7 +26019,7 @@ function createSkillActionTools(skills, context) {
|
|
|
25394
26019
|
if (!skill.actions) continue;
|
|
25395
26020
|
for (const action of skill.actions) {
|
|
25396
26021
|
tools.push(
|
|
25397
|
-
|
|
26022
|
+
tool24(
|
|
25398
26023
|
`${skill.id}__${action.id}`,
|
|
25399
26024
|
action.description,
|
|
25400
26025
|
{
|
|
@@ -25486,10 +26111,10 @@ ${lines.join("\n\n")}`;
|
|
|
25486
26111
|
}
|
|
25487
26112
|
|
|
25488
26113
|
// src/mcp/persona-tools.ts
|
|
25489
|
-
import { tool as
|
|
26114
|
+
import { tool as tool25 } from "@anthropic-ai/claude-agent-sdk";
|
|
25490
26115
|
function createPersonaTools(ctx, marvinDir) {
|
|
25491
26116
|
return [
|
|
25492
|
-
|
|
26117
|
+
tool25(
|
|
25493
26118
|
"set_persona",
|
|
25494
26119
|
"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.",
|
|
25495
26120
|
{
|
|
@@ -25519,7 +26144,7 @@ ${summaries}`
|
|
|
25519
26144
|
};
|
|
25520
26145
|
}
|
|
25521
26146
|
),
|
|
25522
|
-
|
|
26147
|
+
tool25(
|
|
25523
26148
|
"get_persona_guidance",
|
|
25524
26149
|
"Get guidance for a persona without changing the active persona. If no persona is specified, lists all available personas with summaries.",
|
|
25525
26150
|
{
|