mrvn-cli 0.5.1 → 0.5.3
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 +836 -164
- package/dist/index.js.map +1 -1
- package/dist/marvin-serve.js +768 -163
- package/dist/marvin-serve.js.map +1 -1
- package/dist/marvin.js +836 -164
- package/dist/marvin.js.map +1 -1
- package/package.json +1 -1
package/dist/marvin.js
CHANGED
|
@@ -13923,6 +13923,26 @@ config(en_default());
|
|
|
13923
13923
|
|
|
13924
13924
|
// src/plugins/builtin/tools/meetings.ts
|
|
13925
13925
|
import { tool } from "@anthropic-ai/claude-agent-sdk";
|
|
13926
|
+
|
|
13927
|
+
// src/personas/owner.ts
|
|
13928
|
+
var OWNER_SHORT = ["po", "dm", "tl"];
|
|
13929
|
+
var OWNER_LONG = ["product-owner", "delivery-manager", "tech-lead"];
|
|
13930
|
+
var VALID_OWNERS = [...OWNER_SHORT, ...OWNER_LONG];
|
|
13931
|
+
var LONG_TO_SHORT = {
|
|
13932
|
+
"product-owner": "po",
|
|
13933
|
+
"delivery-manager": "dm",
|
|
13934
|
+
"tech-lead": "tl"
|
|
13935
|
+
};
|
|
13936
|
+
var ownerSchema = external_exports.enum(VALID_OWNERS);
|
|
13937
|
+
function normalizeOwner(owner) {
|
|
13938
|
+
if (owner === void 0) return void 0;
|
|
13939
|
+
return LONG_TO_SHORT[owner] ?? owner;
|
|
13940
|
+
}
|
|
13941
|
+
function isValidOwner(value) {
|
|
13942
|
+
return VALID_OWNERS.includes(value);
|
|
13943
|
+
}
|
|
13944
|
+
|
|
13945
|
+
// src/plugins/builtin/tools/meetings.ts
|
|
13926
13946
|
function createMeetingTools(store) {
|
|
13927
13947
|
return [
|
|
13928
13948
|
tool(
|
|
@@ -13978,7 +13998,8 @@ function createMeetingTools(store) {
|
|
|
13978
13998
|
title: external_exports.string().describe("Title of the meeting"),
|
|
13979
13999
|
content: external_exports.string().describe("Meeting agenda, notes, or minutes"),
|
|
13980
14000
|
status: external_exports.string().optional().describe("Status (default: 'scheduled')"),
|
|
13981
|
-
owner:
|
|
14001
|
+
owner: ownerSchema.optional().describe("Persona role responsible (po, dm, tl)"),
|
|
14002
|
+
assignee: external_exports.string().optional().describe("Person assigned to do the work"),
|
|
13982
14003
|
tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization"),
|
|
13983
14004
|
attendees: external_exports.array(external_exports.string()).optional().describe("List of attendees"),
|
|
13984
14005
|
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.")
|
|
@@ -13988,7 +14009,8 @@ function createMeetingTools(store) {
|
|
|
13988
14009
|
title: args.title,
|
|
13989
14010
|
status: args.status ?? "scheduled"
|
|
13990
14011
|
};
|
|
13991
|
-
if (args.owner) frontmatter.owner = args.owner;
|
|
14012
|
+
if (args.owner) frontmatter.owner = normalizeOwner(args.owner);
|
|
14013
|
+
if (args.assignee) frontmatter.assignee = args.assignee;
|
|
13992
14014
|
if (args.tags) frontmatter.tags = args.tags;
|
|
13993
14015
|
if (args.attendees) frontmatter.attendees = args.attendees;
|
|
13994
14016
|
frontmatter.date = args.date;
|
|
@@ -14015,10 +14037,13 @@ function createMeetingTools(store) {
|
|
|
14015
14037
|
title: external_exports.string().optional().describe("New title"),
|
|
14016
14038
|
status: external_exports.string().optional().describe("New status"),
|
|
14017
14039
|
content: external_exports.string().optional().describe("New content"),
|
|
14018
|
-
owner:
|
|
14040
|
+
owner: ownerSchema.optional().describe("Persona role responsible (po, dm, tl)"),
|
|
14041
|
+
assignee: external_exports.string().optional().describe("Person assigned to do the work")
|
|
14019
14042
|
},
|
|
14020
14043
|
async (args) => {
|
|
14021
|
-
const { id, content, ...updates } = args;
|
|
14044
|
+
const { id, content, owner, assignee, ...updates } = args;
|
|
14045
|
+
if (owner !== void 0) updates.owner = normalizeOwner(owner);
|
|
14046
|
+
if (assignee !== void 0) updates.assignee = assignee;
|
|
14022
14047
|
const doc = store.update(id, updates, content);
|
|
14023
14048
|
return {
|
|
14024
14049
|
content: [
|
|
@@ -14634,14 +14659,14 @@ function collectSprintSummaryData(store, sprintId) {
|
|
|
14634
14659
|
const sprintItemIds = new Set(workItemDocs.map((d) => d.frontmatter.id));
|
|
14635
14660
|
for (const doc of workItemDocs) {
|
|
14636
14661
|
const about = doc.frontmatter.aboutArtifact;
|
|
14637
|
-
const
|
|
14662
|
+
const focusTag = (doc.frontmatter.tags ?? []).find((t) => t.startsWith("focus:"));
|
|
14638
14663
|
const item = {
|
|
14639
14664
|
id: doc.frontmatter.id,
|
|
14640
14665
|
title: doc.frontmatter.title,
|
|
14641
14666
|
type: doc.frontmatter.type,
|
|
14642
14667
|
status: doc.frontmatter.status,
|
|
14643
14668
|
progress: getEffectiveProgress(doc.frontmatter),
|
|
14644
|
-
|
|
14669
|
+
workFocus: focusTag ? focusTag.slice(6) : void 0,
|
|
14645
14670
|
aboutArtifact: about
|
|
14646
14671
|
};
|
|
14647
14672
|
allItemsById.set(item.id, item);
|
|
@@ -15796,7 +15821,8 @@ function createFeatureTools(store) {
|
|
|
15796
15821
|
title: external_exports.string().describe("Feature title"),
|
|
15797
15822
|
content: external_exports.string().describe("Feature description and requirements"),
|
|
15798
15823
|
status: external_exports.enum(["draft", "approved", "deferred", "done"]).optional().describe("Feature status (default: 'draft')"),
|
|
15799
|
-
owner:
|
|
15824
|
+
owner: ownerSchema.optional().describe("Persona role responsible (po, dm, tl)"),
|
|
15825
|
+
assignee: external_exports.string().optional().describe("Person assigned to do the work"),
|
|
15800
15826
|
priority: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("Feature priority"),
|
|
15801
15827
|
tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization")
|
|
15802
15828
|
},
|
|
@@ -15805,7 +15831,8 @@ function createFeatureTools(store) {
|
|
|
15805
15831
|
title: args.title,
|
|
15806
15832
|
status: args.status ?? "draft"
|
|
15807
15833
|
};
|
|
15808
|
-
if (args.owner) frontmatter.owner = args.owner;
|
|
15834
|
+
if (args.owner) frontmatter.owner = normalizeOwner(args.owner);
|
|
15835
|
+
if (args.assignee) frontmatter.assignee = args.assignee;
|
|
15809
15836
|
if (args.priority) frontmatter.priority = args.priority;
|
|
15810
15837
|
if (args.tags) frontmatter.tags = args.tags;
|
|
15811
15838
|
const doc = store.create("feature", frontmatter, args.content);
|
|
@@ -15827,12 +15854,15 @@ function createFeatureTools(store) {
|
|
|
15827
15854
|
title: external_exports.string().optional().describe("New title"),
|
|
15828
15855
|
status: external_exports.enum(["draft", "approved", "deferred", "done"]).optional().describe("New status"),
|
|
15829
15856
|
content: external_exports.string().optional().describe("New content"),
|
|
15830
|
-
owner:
|
|
15857
|
+
owner: ownerSchema.optional().describe("Persona role responsible (po, dm, tl)"),
|
|
15858
|
+
assignee: external_exports.string().optional().describe("Person assigned to do the work"),
|
|
15831
15859
|
priority: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("New priority"),
|
|
15832
15860
|
tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove 'risk', add 'risk-mitigated')")
|
|
15833
15861
|
},
|
|
15834
15862
|
async (args) => {
|
|
15835
|
-
const { id, content, ...updates } = args;
|
|
15863
|
+
const { id, content, owner, assignee, ...updates } = args;
|
|
15864
|
+
if (owner !== void 0) updates.owner = normalizeOwner(owner);
|
|
15865
|
+
if (assignee !== void 0) updates.assignee = assignee;
|
|
15836
15866
|
const doc = store.update(id, updates, content);
|
|
15837
15867
|
return {
|
|
15838
15868
|
content: [
|
|
@@ -15930,7 +15960,8 @@ function createEpicTools(store) {
|
|
|
15930
15960
|
content: external_exports.string().describe("Epic description and scope"),
|
|
15931
15961
|
linkedFeature: linkedFeatureArray.describe("Feature ID(s) to link this epic to (e.g. ['F-001'] or ['F-001', 'F-002'])"),
|
|
15932
15962
|
status: external_exports.enum(["planned", "in-progress", "done"]).optional().describe("Epic status (default: 'planned')"),
|
|
15933
|
-
owner:
|
|
15963
|
+
owner: ownerSchema.optional().describe("Persona role responsible (po, dm, tl)"),
|
|
15964
|
+
assignee: external_exports.string().optional().describe("Person assigned to do the work"),
|
|
15934
15965
|
targetDate: external_exports.string().optional().describe("Target completion date (ISO format)"),
|
|
15935
15966
|
estimatedEffort: external_exports.string().optional().describe("Estimated effort (e.g. '2 weeks', '5 story points')"),
|
|
15936
15967
|
tags: external_exports.array(external_exports.string()).optional().describe("Additional tags")
|
|
@@ -15979,7 +16010,8 @@ function createEpicTools(store) {
|
|
|
15979
16010
|
linkedFeature: linkedFeatures,
|
|
15980
16011
|
tags: [...generateFeatureTags(linkedFeatures), ...args.tags ?? []]
|
|
15981
16012
|
};
|
|
15982
|
-
if (args.owner) frontmatter.owner = args.owner;
|
|
16013
|
+
if (args.owner) frontmatter.owner = normalizeOwner(args.owner);
|
|
16014
|
+
if (args.assignee) frontmatter.assignee = args.assignee;
|
|
15983
16015
|
if (args.targetDate) frontmatter.targetDate = args.targetDate;
|
|
15984
16016
|
if (args.estimatedEffort) frontmatter.estimatedEffort = args.estimatedEffort;
|
|
15985
16017
|
const doc = store.create("epic", frontmatter, args.content);
|
|
@@ -16001,14 +16033,17 @@ function createEpicTools(store) {
|
|
|
16001
16033
|
title: external_exports.string().optional().describe("New title"),
|
|
16002
16034
|
status: external_exports.enum(["planned", "in-progress", "done"]).optional().describe("New status"),
|
|
16003
16035
|
content: external_exports.string().optional().describe("New content"),
|
|
16004
|
-
owner:
|
|
16036
|
+
owner: ownerSchema.optional().describe("Persona role responsible (po, dm, tl)"),
|
|
16037
|
+
assignee: external_exports.string().optional().describe("Person assigned to do the work"),
|
|
16005
16038
|
targetDate: external_exports.string().optional().describe("New target date"),
|
|
16006
16039
|
estimatedEffort: external_exports.string().optional().describe("New estimated effort"),
|
|
16007
16040
|
linkedFeature: linkedFeatureArray.optional().describe("New linked feature ID(s)"),
|
|
16008
16041
|
tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove 'risk', add 'risk-mitigated')")
|
|
16009
16042
|
},
|
|
16010
16043
|
async (args) => {
|
|
16011
|
-
const { id, content, linkedFeature: rawLinkedFeature, tags: userTags, ...updates } = args;
|
|
16044
|
+
const { id, content, linkedFeature: rawLinkedFeature, tags: userTags, owner, assignee, ...updates } = args;
|
|
16045
|
+
if (owner !== void 0) updates.owner = normalizeOwner(owner);
|
|
16046
|
+
if (assignee !== void 0) updates.assignee = assignee;
|
|
16012
16047
|
if (rawLinkedFeature !== void 0) {
|
|
16013
16048
|
const linkedFeatures = normalizeLinkedFeatures(rawLinkedFeature);
|
|
16014
16049
|
for (const featureId of linkedFeatures) {
|
|
@@ -16135,7 +16170,7 @@ function createContributionTools(store) {
|
|
|
16135
16170
|
aboutArtifact: external_exports.string().describe("Artifact ID this contribution relates to (e.g. 'A-001', 'T-003')"),
|
|
16136
16171
|
status: external_exports.string().optional().describe("Status (default: 'done')"),
|
|
16137
16172
|
tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization"),
|
|
16138
|
-
|
|
16173
|
+
workFocus: external_exports.string().optional().describe("Work focus name (e.g. 'Budget UX'). Adds a focus:<value> tag."),
|
|
16139
16174
|
parentProgress: external_exports.number().optional().describe("Set progress (0-100) on the parent artifact (e.g. task or action). Propagates up the hierarchy.")
|
|
16140
16175
|
},
|
|
16141
16176
|
async (args) => {
|
|
@@ -16147,7 +16182,7 @@ function createContributionTools(store) {
|
|
|
16147
16182
|
};
|
|
16148
16183
|
frontmatter.aboutArtifact = args.aboutArtifact;
|
|
16149
16184
|
const tags = [...args.tags ?? []];
|
|
16150
|
-
if (args.
|
|
16185
|
+
if (args.workFocus) tags.push(`focus:${args.workFocus}`);
|
|
16151
16186
|
if (tags.length > 0) frontmatter.tags = tags;
|
|
16152
16187
|
const doc = store.create("contribution", frontmatter, args.content);
|
|
16153
16188
|
const progressParts = [];
|
|
@@ -16203,15 +16238,15 @@ function createContributionTools(store) {
|
|
|
16203
16238
|
title: external_exports.string().optional().describe("New title"),
|
|
16204
16239
|
status: external_exports.string().optional().describe("New status"),
|
|
16205
16240
|
content: external_exports.string().optional().describe("New content"),
|
|
16206
|
-
|
|
16241
|
+
workFocus: external_exports.string().optional().describe("Work focus name (e.g. 'Budget UX'). Replaces existing focus:<value> tag.")
|
|
16207
16242
|
},
|
|
16208
16243
|
async (args) => {
|
|
16209
|
-
const { id, content,
|
|
16210
|
-
if (
|
|
16244
|
+
const { id, content, workFocus, ...updates } = args;
|
|
16245
|
+
if (workFocus !== void 0) {
|
|
16211
16246
|
const existing = store.get(id);
|
|
16212
16247
|
const existingTags = existing?.frontmatter.tags ?? [];
|
|
16213
|
-
const filtered = existingTags.filter((t) => !t.startsWith("
|
|
16214
|
-
filtered.push(`
|
|
16248
|
+
const filtered = existingTags.filter((t) => !t.startsWith("focus:"));
|
|
16249
|
+
filtered.push(`focus:${workFocus}`);
|
|
16215
16250
|
updates.tags = filtered;
|
|
16216
16251
|
}
|
|
16217
16252
|
const oldDoc = store.get(id);
|
|
@@ -16694,7 +16729,7 @@ function createTaskTools(store) {
|
|
|
16694
16729
|
complexity: external_exports.enum(["trivial", "simple", "moderate", "complex", "very-complex"]).optional().describe("Task complexity"),
|
|
16695
16730
|
priority: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("Task priority"),
|
|
16696
16731
|
tags: external_exports.array(external_exports.string()).optional().describe("Additional tags"),
|
|
16697
|
-
|
|
16732
|
+
workFocus: external_exports.string().optional().describe("Work focus name (e.g. 'Budget UX'). Adds a focus:<value> tag.")
|
|
16698
16733
|
},
|
|
16699
16734
|
async (args) => {
|
|
16700
16735
|
const linkedEpics = normalizeLinkedEpics(args.linkedEpic);
|
|
@@ -16708,7 +16743,7 @@ function createTaskTools(store) {
|
|
|
16708
16743
|
}
|
|
16709
16744
|
}
|
|
16710
16745
|
const baseTags = [...generateEpicTags(linkedEpics), ...args.tags ?? []];
|
|
16711
|
-
if (args.
|
|
16746
|
+
if (args.workFocus) baseTags.push(`focus:${args.workFocus}`);
|
|
16712
16747
|
const frontmatter = {
|
|
16713
16748
|
title: args.title,
|
|
16714
16749
|
status: args.status ?? "backlog",
|
|
@@ -16749,11 +16784,11 @@ function createTaskTools(store) {
|
|
|
16749
16784
|
complexity: external_exports.enum(["trivial", "simple", "moderate", "complex", "very-complex"]).optional().describe("New complexity"),
|
|
16750
16785
|
priority: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("New priority"),
|
|
16751
16786
|
tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove old tags, add new ones)"),
|
|
16752
|
-
|
|
16787
|
+
workFocus: external_exports.string().optional().describe("Work focus name (e.g. 'Budget UX'). Replaces existing focus:<value> tag."),
|
|
16753
16788
|
progress: external_exports.number().optional().describe("Explicit progress percentage (0-100). Overrides auto-calculation from child contributions.")
|
|
16754
16789
|
},
|
|
16755
16790
|
async (args) => {
|
|
16756
|
-
const { id, content, linkedEpic: rawLinkedEpic, tags: userTags,
|
|
16791
|
+
const { id, content, linkedEpic: rawLinkedEpic, tags: userTags, workFocus, progress, ...updates } = args;
|
|
16757
16792
|
const warnings = [];
|
|
16758
16793
|
if (rawLinkedEpic !== void 0) {
|
|
16759
16794
|
const linkedEpics = normalizeLinkedEpics(rawLinkedEpic);
|
|
@@ -16774,10 +16809,10 @@ function createTaskTools(store) {
|
|
|
16774
16809
|
} else if (userTags !== void 0) {
|
|
16775
16810
|
updates.tags = userTags;
|
|
16776
16811
|
}
|
|
16777
|
-
if (
|
|
16812
|
+
if (workFocus !== void 0) {
|
|
16778
16813
|
const currentTags = updates.tags ?? store.get(id)?.frontmatter.tags ?? [];
|
|
16779
|
-
const filtered = currentTags.filter((t) => !t.startsWith("
|
|
16780
|
-
filtered.push(`
|
|
16814
|
+
const filtered = currentTags.filter((t) => !t.startsWith("focus:"));
|
|
16815
|
+
filtered.push(`focus:${workFocus}`);
|
|
16781
16816
|
updates.tags = filtered;
|
|
16782
16817
|
}
|
|
16783
16818
|
if (typeof progress === "number") {
|
|
@@ -18635,7 +18670,8 @@ function createDecisionTools(store) {
|
|
|
18635
18670
|
title: external_exports.string().describe("Title of the decision"),
|
|
18636
18671
|
content: external_exports.string().describe("Decision description, context, and rationale"),
|
|
18637
18672
|
status: external_exports.enum(["open", "decided", "superseded", "dismissed"]).optional().describe("Status (default: 'open')"),
|
|
18638
|
-
owner:
|
|
18673
|
+
owner: ownerSchema.optional().describe("Persona role responsible (po, dm, tl)"),
|
|
18674
|
+
assignee: external_exports.string().optional().describe("Person assigned to do the work"),
|
|
18639
18675
|
tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization")
|
|
18640
18676
|
},
|
|
18641
18677
|
async (args) => {
|
|
@@ -18644,7 +18680,8 @@ function createDecisionTools(store) {
|
|
|
18644
18680
|
{
|
|
18645
18681
|
title: args.title,
|
|
18646
18682
|
status: args.status,
|
|
18647
|
-
owner: args.owner,
|
|
18683
|
+
owner: normalizeOwner(args.owner),
|
|
18684
|
+
assignee: args.assignee,
|
|
18648
18685
|
tags: args.tags
|
|
18649
18686
|
},
|
|
18650
18687
|
args.content
|
|
@@ -18667,11 +18704,14 @@ function createDecisionTools(store) {
|
|
|
18667
18704
|
title: external_exports.string().optional().describe("New title"),
|
|
18668
18705
|
status: external_exports.enum(["open", "decided", "superseded", "dismissed"]).optional().describe("New status"),
|
|
18669
18706
|
content: external_exports.string().optional().describe("New content"),
|
|
18670
|
-
owner:
|
|
18707
|
+
owner: ownerSchema.optional().describe("Persona role responsible (po, dm, tl)"),
|
|
18708
|
+
assignee: external_exports.string().optional().describe("Person assigned to do the work"),
|
|
18671
18709
|
tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove 'risk', add 'risk-mitigated')")
|
|
18672
18710
|
},
|
|
18673
18711
|
async (args) => {
|
|
18674
|
-
const { id, content, ...updates } = args;
|
|
18712
|
+
const { id, content, owner, assignee, ...updates } = args;
|
|
18713
|
+
if (owner !== void 0) updates.owner = normalizeOwner(owner);
|
|
18714
|
+
if (assignee !== void 0) updates.assignee = assignee;
|
|
18675
18715
|
const doc = store.update(id, updates, content);
|
|
18676
18716
|
return {
|
|
18677
18717
|
content: [
|
|
@@ -18769,12 +18809,13 @@ function createActionTools(store) {
|
|
|
18769
18809
|
title: external_exports.string().describe("Title of the action item"),
|
|
18770
18810
|
content: external_exports.string().describe("Description of what needs to be done"),
|
|
18771
18811
|
status: external_exports.string().optional().describe("Status (default: 'open')"),
|
|
18772
|
-
owner:
|
|
18812
|
+
owner: ownerSchema.optional().describe("Persona role responsible (po, dm, tl)"),
|
|
18813
|
+
assignee: external_exports.string().optional().describe("Person assigned to do the work"),
|
|
18773
18814
|
priority: external_exports.string().optional().describe("Priority (high, medium, low)"),
|
|
18774
18815
|
tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization"),
|
|
18775
18816
|
dueDate: external_exports.string().optional().describe("Due date in ISO format (e.g. '2026-03-15')"),
|
|
18776
18817
|
sprints: external_exports.array(external_exports.string()).optional().describe("Sprint IDs to assign (e.g. ['SP-001']). Adds sprint:SP-xxx tags."),
|
|
18777
|
-
|
|
18818
|
+
workFocus: external_exports.string().optional().describe("Work focus name (e.g. 'Budget UX'). Adds a focus:<value> tag.")
|
|
18778
18819
|
},
|
|
18779
18820
|
async (args) => {
|
|
18780
18821
|
const tags = [...args.tags ?? []];
|
|
@@ -18784,15 +18825,16 @@ function createActionTools(store) {
|
|
|
18784
18825
|
if (!tags.includes(tag)) tags.push(tag);
|
|
18785
18826
|
}
|
|
18786
18827
|
}
|
|
18787
|
-
if (args.
|
|
18788
|
-
tags.push(`
|
|
18828
|
+
if (args.workFocus) {
|
|
18829
|
+
tags.push(`focus:${args.workFocus}`);
|
|
18789
18830
|
}
|
|
18790
18831
|
const doc = store.create(
|
|
18791
18832
|
"action",
|
|
18792
18833
|
{
|
|
18793
18834
|
title: args.title,
|
|
18794
18835
|
status: args.status,
|
|
18795
|
-
owner: args.owner,
|
|
18836
|
+
owner: normalizeOwner(args.owner),
|
|
18837
|
+
assignee: args.assignee,
|
|
18796
18838
|
priority: args.priority,
|
|
18797
18839
|
tags: tags.length > 0 ? tags : void 0,
|
|
18798
18840
|
dueDate: args.dueDate
|
|
@@ -18820,16 +18862,19 @@ function createActionTools(store) {
|
|
|
18820
18862
|
title: external_exports.string().optional().describe("New title"),
|
|
18821
18863
|
status: external_exports.string().optional().describe("New status"),
|
|
18822
18864
|
content: external_exports.string().optional().describe("New content"),
|
|
18823
|
-
owner:
|
|
18865
|
+
owner: ownerSchema.optional().describe("Persona role responsible (po, dm, tl)"),
|
|
18866
|
+
assignee: external_exports.string().optional().describe("Person assigned to do the work"),
|
|
18824
18867
|
priority: external_exports.string().optional().describe("New priority"),
|
|
18825
18868
|
dueDate: external_exports.string().optional().describe("Due date in ISO format (e.g. '2026-03-15')"),
|
|
18826
18869
|
tags: external_exports.array(external_exports.string()).optional().describe("Replace all tags. When provided with sprints, sprint tags are merged into this array."),
|
|
18827
18870
|
sprints: external_exports.array(external_exports.string()).optional().describe("Sprint IDs to assign (replaces existing sprint tags). E.g. ['SP-001']."),
|
|
18828
|
-
|
|
18871
|
+
workFocus: external_exports.string().optional().describe("Work focus name (e.g. 'Budget UX'). Replaces existing focus:<value> tag."),
|
|
18829
18872
|
progress: external_exports.number().optional().describe("Explicit progress percentage (0-100).")
|
|
18830
18873
|
},
|
|
18831
18874
|
async (args) => {
|
|
18832
|
-
const { id, content, sprints, tags,
|
|
18875
|
+
const { id, content, sprints, tags, workFocus, progress, owner, assignee, ...updates } = args;
|
|
18876
|
+
if (owner !== void 0) updates.owner = normalizeOwner(owner);
|
|
18877
|
+
if (assignee !== void 0) updates.assignee = assignee;
|
|
18833
18878
|
if (tags !== void 0) {
|
|
18834
18879
|
const merged = [...tags];
|
|
18835
18880
|
if (sprints) {
|
|
@@ -18838,14 +18883,14 @@ function createActionTools(store) {
|
|
|
18838
18883
|
if (!merged.includes(tag)) merged.push(tag);
|
|
18839
18884
|
}
|
|
18840
18885
|
}
|
|
18841
|
-
if (
|
|
18842
|
-
const filtered = merged.filter((t) => !t.startsWith("
|
|
18843
|
-
filtered.push(`
|
|
18886
|
+
if (workFocus !== void 0) {
|
|
18887
|
+
const filtered = merged.filter((t) => !t.startsWith("focus:"));
|
|
18888
|
+
filtered.push(`focus:${workFocus}`);
|
|
18844
18889
|
updates.tags = filtered;
|
|
18845
18890
|
} else {
|
|
18846
18891
|
updates.tags = merged;
|
|
18847
18892
|
}
|
|
18848
|
-
} else if (sprints !== void 0 ||
|
|
18893
|
+
} else if (sprints !== void 0 || workFocus !== void 0) {
|
|
18849
18894
|
const existing = store.get(id);
|
|
18850
18895
|
if (!existing) {
|
|
18851
18896
|
return {
|
|
@@ -18858,9 +18903,9 @@ function createActionTools(store) {
|
|
|
18858
18903
|
existingTags = existingTags.filter((t) => !t.startsWith("sprint:"));
|
|
18859
18904
|
existingTags.push(...sprints.map((s) => `sprint:${s}`));
|
|
18860
18905
|
}
|
|
18861
|
-
if (
|
|
18862
|
-
existingTags = existingTags.filter((t) => !t.startsWith("
|
|
18863
|
-
existingTags.push(`
|
|
18906
|
+
if (workFocus !== void 0) {
|
|
18907
|
+
existingTags = existingTags.filter((t) => !t.startsWith("focus:"));
|
|
18908
|
+
existingTags.push(`focus:${workFocus}`);
|
|
18864
18909
|
}
|
|
18865
18910
|
updates.tags = existingTags;
|
|
18866
18911
|
}
|
|
@@ -18973,7 +19018,8 @@ function createQuestionTools(store) {
|
|
|
18973
19018
|
title: external_exports.string().describe("The question being asked"),
|
|
18974
19019
|
content: external_exports.string().describe("Context and details about the question"),
|
|
18975
19020
|
status: external_exports.string().optional().describe("Status (default: 'open')"),
|
|
18976
|
-
owner:
|
|
19021
|
+
owner: ownerSchema.optional().describe("Persona role responsible (po, dm, tl)"),
|
|
19022
|
+
assignee: external_exports.string().optional().describe("Person assigned to do the work"),
|
|
18977
19023
|
tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization")
|
|
18978
19024
|
},
|
|
18979
19025
|
async (args) => {
|
|
@@ -18982,7 +19028,8 @@ function createQuestionTools(store) {
|
|
|
18982
19028
|
{
|
|
18983
19029
|
title: args.title,
|
|
18984
19030
|
status: args.status,
|
|
18985
|
-
owner: args.owner,
|
|
19031
|
+
owner: normalizeOwner(args.owner),
|
|
19032
|
+
assignee: args.assignee,
|
|
18986
19033
|
tags: args.tags
|
|
18987
19034
|
},
|
|
18988
19035
|
args.content
|
|
@@ -19005,11 +19052,14 @@ function createQuestionTools(store) {
|
|
|
19005
19052
|
title: external_exports.string().optional().describe("New title"),
|
|
19006
19053
|
status: external_exports.string().optional().describe("New status (e.g. 'answered')"),
|
|
19007
19054
|
content: external_exports.string().optional().describe("Updated content / answer"),
|
|
19008
|
-
owner:
|
|
19055
|
+
owner: ownerSchema.optional().describe("Persona role responsible (po, dm, tl)"),
|
|
19056
|
+
assignee: external_exports.string().optional().describe("Person assigned to do the work"),
|
|
19009
19057
|
tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove 'risk', add 'risk-mitigated')")
|
|
19010
19058
|
},
|
|
19011
19059
|
async (args) => {
|
|
19012
|
-
const { id, content, ...updates } = args;
|
|
19060
|
+
const { id, content, owner, assignee, ...updates } = args;
|
|
19061
|
+
if (owner !== void 0) updates.owner = normalizeOwner(owner);
|
|
19062
|
+
if (assignee !== void 0) updates.assignee = assignee;
|
|
19013
19063
|
const doc = store.update(id, updates, content);
|
|
19014
19064
|
return {
|
|
19015
19065
|
content: [
|
|
@@ -19034,18 +19084,20 @@ function createDocumentTools(store) {
|
|
|
19034
19084
|
{
|
|
19035
19085
|
type: external_exports.string().optional().describe(`Filter by document type (registered types: ${store.registeredTypes.join(", ")})`),
|
|
19036
19086
|
status: external_exports.string().optional().describe("Filter by status"),
|
|
19087
|
+
owner: external_exports.string().optional().describe("Filter by persona role owner (po, dm, tl)"),
|
|
19037
19088
|
tag: external_exports.string().optional().describe("Filter by tag"),
|
|
19038
|
-
|
|
19089
|
+
workFocus: external_exports.string().optional().describe("Filter by work focus name (matches focus:<value> tag)")
|
|
19039
19090
|
},
|
|
19040
19091
|
async (args) => {
|
|
19041
19092
|
let docs = store.list({
|
|
19042
19093
|
type: args.type,
|
|
19043
19094
|
status: args.status,
|
|
19095
|
+
owner: args.owner,
|
|
19044
19096
|
tag: args.tag
|
|
19045
19097
|
});
|
|
19046
|
-
if (args.
|
|
19047
|
-
const
|
|
19048
|
-
docs = docs.filter((d) => d.frontmatter.tags?.includes(
|
|
19098
|
+
if (args.workFocus) {
|
|
19099
|
+
const focusTag = `focus:${args.workFocus}`;
|
|
19100
|
+
docs = docs.filter((d) => d.frontmatter.tags?.includes(focusTag));
|
|
19049
19101
|
}
|
|
19050
19102
|
const summary = docs.map((d) => ({
|
|
19051
19103
|
id: d.frontmatter.id,
|
|
@@ -21043,6 +21095,39 @@ tr:hover td {
|
|
|
21043
21095
|
font-weight: 700;
|
|
21044
21096
|
color: var(--text);
|
|
21045
21097
|
}
|
|
21098
|
+
|
|
21099
|
+
/* Focus-grouped work items */
|
|
21100
|
+
.focus-row td:first-child {
|
|
21101
|
+
border-left: 3px solid var(--focus-color, var(--border));
|
|
21102
|
+
}
|
|
21103
|
+
|
|
21104
|
+
.focus-group-header td {
|
|
21105
|
+
background: var(--bg-hover);
|
|
21106
|
+
border-left: 3px solid var(--focus-color, var(--border));
|
|
21107
|
+
padding-top: 0.5rem;
|
|
21108
|
+
padding-bottom: 0.5rem;
|
|
21109
|
+
border-bottom: 1px solid var(--border);
|
|
21110
|
+
}
|
|
21111
|
+
|
|
21112
|
+
.focus-group-header td:first-child {
|
|
21113
|
+
border-left-width: 3px;
|
|
21114
|
+
}
|
|
21115
|
+
|
|
21116
|
+
.focus-group-name {
|
|
21117
|
+
font-weight: 600;
|
|
21118
|
+
font-size: 0.8rem;
|
|
21119
|
+
color: var(--text);
|
|
21120
|
+
margin-right: 0.75rem;
|
|
21121
|
+
}
|
|
21122
|
+
|
|
21123
|
+
.focus-group-stats {
|
|
21124
|
+
font-size: 0.75rem;
|
|
21125
|
+
color: var(--text-dim);
|
|
21126
|
+
}
|
|
21127
|
+
|
|
21128
|
+
.focus-group-progress {
|
|
21129
|
+
width: 96px;
|
|
21130
|
+
}
|
|
21046
21131
|
`;
|
|
21047
21132
|
}
|
|
21048
21133
|
|
|
@@ -22509,15 +22594,15 @@ function sprintSummaryPage(data, cached2) {
|
|
|
22509
22594
|
</div>`,
|
|
22510
22595
|
{ titleTag: "h3" }
|
|
22511
22596
|
) : "";
|
|
22512
|
-
const
|
|
22513
|
-
"
|
|
22514
|
-
"
|
|
22515
|
-
"
|
|
22516
|
-
"
|
|
22517
|
-
"
|
|
22518
|
-
"
|
|
22519
|
-
"
|
|
22520
|
-
"
|
|
22597
|
+
const FOCUS_BORDER_PALETTE = [
|
|
22598
|
+
"hsl(220, 60%, 55%)",
|
|
22599
|
+
"hsl(160, 50%, 45%)",
|
|
22600
|
+
"hsl(280, 45%, 55%)",
|
|
22601
|
+
"hsl(30, 65%, 55%)",
|
|
22602
|
+
"hsl(340, 50%, 55%)",
|
|
22603
|
+
"hsl(190, 50%, 45%)",
|
|
22604
|
+
"hsl(60, 50%, 50%)",
|
|
22605
|
+
"hsl(120, 40%, 45%)"
|
|
22521
22606
|
];
|
|
22522
22607
|
function hashString(s) {
|
|
22523
22608
|
let h = 0;
|
|
@@ -22526,68 +22611,92 @@ function sprintSummaryPage(data, cached2) {
|
|
|
22526
22611
|
}
|
|
22527
22612
|
return Math.abs(h);
|
|
22528
22613
|
}
|
|
22529
|
-
|
|
22530
|
-
|
|
22531
|
-
|
|
22532
|
-
|
|
22533
|
-
|
|
22534
|
-
|
|
22614
|
+
const focusGroups = /* @__PURE__ */ new Map();
|
|
22615
|
+
for (const item of data.workItems.items) {
|
|
22616
|
+
const focus = item.workFocus ?? "Unassigned";
|
|
22617
|
+
if (!focusGroups.has(focus)) focusGroups.set(focus, []);
|
|
22618
|
+
focusGroups.get(focus).push(item);
|
|
22619
|
+
}
|
|
22620
|
+
const focusColorMap = /* @__PURE__ */ new Map();
|
|
22621
|
+
for (const name of focusGroups.keys()) {
|
|
22622
|
+
focusColorMap.set(name, FOCUS_BORDER_PALETTE[hashString(name) % FOCUS_BORDER_PALETTE.length]);
|
|
22623
|
+
}
|
|
22624
|
+
function countFocusStats(items) {
|
|
22625
|
+
let total = 0;
|
|
22626
|
+
let done = 0;
|
|
22627
|
+
let inProgress = 0;
|
|
22628
|
+
function walk(list) {
|
|
22629
|
+
for (const w of list) {
|
|
22630
|
+
if (w.type !== "contribution") {
|
|
22631
|
+
total++;
|
|
22632
|
+
const s = w.status.toLowerCase();
|
|
22633
|
+
if (s === "done" || s === "closed" || s === "resolved" || s === "decided") done++;
|
|
22634
|
+
else if (s === "in-progress" || s === "in progress") inProgress++;
|
|
22635
|
+
}
|
|
22636
|
+
if (w.children) walk(w.children);
|
|
22535
22637
|
}
|
|
22536
22638
|
}
|
|
22537
|
-
|
|
22538
|
-
|
|
22539
|
-
const uniqueStreams = collectStreams(data.workItems.items);
|
|
22540
|
-
const streamColorMap = /* @__PURE__ */ new Map();
|
|
22541
|
-
for (const name of uniqueStreams) {
|
|
22542
|
-
streamColorMap.set(name, STREAM_PALETTE[hashString(name) % STREAM_PALETTE.length]);
|
|
22639
|
+
walk(items);
|
|
22640
|
+
return { total, done, inProgress };
|
|
22543
22641
|
}
|
|
22544
|
-
|
|
22545
|
-
const streamStyleBlock = streamStyleRules ? `<style>${streamStyleRules}</style>` : "";
|
|
22546
|
-
function renderItemRows(items, depth = 0) {
|
|
22642
|
+
function renderItemRows(items, borderColor, depth = 0) {
|
|
22547
22643
|
return items.flatMap((w) => {
|
|
22548
22644
|
const isChild = depth > 0;
|
|
22549
22645
|
const isContribution = w.type === "contribution";
|
|
22550
|
-
const classes = [];
|
|
22646
|
+
const classes = ["focus-row"];
|
|
22551
22647
|
if (isContribution) classes.push("contribution-row");
|
|
22552
22648
|
else if (isChild) classes.push("child-row");
|
|
22553
|
-
const dataStream = w.workStream ? ` data-stream="${escapeHtml(w.workStream)}"` : "";
|
|
22554
|
-
const rowAttrs = classes.length > 0 ? ` class="${classes.join(" ")}"` : "";
|
|
22555
22649
|
const indent = depth > 0 ? ` style="padding-left: ${0.75 + depth * 1}rem"` : "";
|
|
22556
|
-
const streamCell = w.workStream ? `<span class="badge badge-subtle">${escapeHtml(w.workStream)}</span>` : "";
|
|
22557
22650
|
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>` : "";
|
|
22558
22651
|
const row = `
|
|
22559
|
-
<tr${
|
|
22652
|
+
<tr class="${classes.join(" ")}" style="--focus-color: ${borderColor}">
|
|
22560
22653
|
<td${indent}><a href="/docs/${escapeHtml(w.type)}/${escapeHtml(w.id)}">${escapeHtml(w.id)}</a></td>
|
|
22561
22654
|
<td>${escapeHtml(w.title)}</td>
|
|
22562
|
-
<td>${streamCell}</td>
|
|
22563
|
-
<td>${escapeHtml(typeLabel(w.type))}</td>
|
|
22564
22655
|
<td>${statusBadge(w.status)}</td>
|
|
22565
22656
|
<td>${progressCell}</td>
|
|
22566
22657
|
</tr>`;
|
|
22567
|
-
const childRows = w.children ? renderItemRows(w.children, depth + 1) : [];
|
|
22658
|
+
const childRows = w.children ? renderItemRows(w.children, borderColor, depth + 1) : [];
|
|
22568
22659
|
return [row, ...childRows];
|
|
22569
22660
|
});
|
|
22570
22661
|
}
|
|
22571
|
-
const
|
|
22572
|
-
const
|
|
22573
|
-
|
|
22574
|
-
|
|
22575
|
-
|
|
22576
|
-
|
|
22577
|
-
|
|
22578
|
-
|
|
22662
|
+
const allWorkItemRows = [];
|
|
22663
|
+
for (const [focus, items] of focusGroups) {
|
|
22664
|
+
const color = focusColorMap.get(focus);
|
|
22665
|
+
const stats = countFocusStats(items);
|
|
22666
|
+
const pct = stats.total > 0 ? Math.round(stats.done / stats.total * 100) : 0;
|
|
22667
|
+
const summaryParts = [];
|
|
22668
|
+
if (stats.done > 0) summaryParts.push(`${stats.done} done`);
|
|
22669
|
+
if (stats.inProgress > 0) summaryParts.push(`${stats.inProgress} in progress`);
|
|
22670
|
+
const remaining = stats.total - stats.done - stats.inProgress;
|
|
22671
|
+
if (remaining > 0) summaryParts.push(`${remaining} open`);
|
|
22672
|
+
allWorkItemRows.push(`
|
|
22673
|
+
<tr class="focus-group-header" style="--focus-color: ${color}">
|
|
22674
|
+
<td colspan="2">
|
|
22675
|
+
<span class="focus-group-name">${escapeHtml(focus)}</span>
|
|
22676
|
+
<span class="focus-group-stats">${summaryParts.join(" / ")}</span>
|
|
22677
|
+
</td>
|
|
22678
|
+
<td colspan="2">
|
|
22679
|
+
<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>
|
|
22680
|
+
</td>
|
|
22681
|
+
</tr>`);
|
|
22682
|
+
allWorkItemRows.push(...renderItemRows(items, color));
|
|
22683
|
+
}
|
|
22684
|
+
const tableHeaders = `<tr>
|
|
22685
|
+
<th>ID</th>
|
|
22686
|
+
<th>Title</th>
|
|
22687
|
+
<th>Status</th>
|
|
22688
|
+
<th>Progress</th>
|
|
22579
22689
|
</tr>`;
|
|
22580
|
-
const workItemsSection =
|
|
22690
|
+
const workItemsSection = allWorkItemRows.length > 0 ? collapsibleSection(
|
|
22581
22691
|
"ss-work-items",
|
|
22582
22692
|
"Work Items",
|
|
22583
|
-
|
|
22584
|
-
<div class="table-wrap">
|
|
22693
|
+
`<div class="table-wrap">
|
|
22585
22694
|
<table id="work-items-table">
|
|
22586
22695
|
<thead>
|
|
22587
|
-
${
|
|
22696
|
+
${tableHeaders}
|
|
22588
22697
|
</thead>
|
|
22589
22698
|
<tbody>
|
|
22590
|
-
${
|
|
22699
|
+
${allWorkItemRows.join("")}
|
|
22591
22700
|
</tbody>
|
|
22592
22701
|
</table>
|
|
22593
22702
|
</div>`,
|
|
@@ -22663,61 +22772,6 @@ function sprintSummaryPage(data, cached2) {
|
|
|
22663
22772
|
</div>
|
|
22664
22773
|
|
|
22665
22774
|
<script>
|
|
22666
|
-
var _sortCol = -1;
|
|
22667
|
-
var _sortAsc = true;
|
|
22668
|
-
|
|
22669
|
-
function sortWorkItems(col) {
|
|
22670
|
-
var table = document.getElementById('work-items-table');
|
|
22671
|
-
if (!table) return;
|
|
22672
|
-
var tbody = table.querySelector('tbody');
|
|
22673
|
-
var allRows = Array.from(tbody.querySelectorAll('tr'));
|
|
22674
|
-
|
|
22675
|
-
// Toggle direction if clicking the same column
|
|
22676
|
-
if (_sortCol === col) {
|
|
22677
|
-
_sortAsc = !_sortAsc;
|
|
22678
|
-
} else {
|
|
22679
|
-
_sortCol = col;
|
|
22680
|
-
_sortAsc = true;
|
|
22681
|
-
}
|
|
22682
|
-
|
|
22683
|
-
// Update sort arrows
|
|
22684
|
-
for (var i = 0; i < 6; i++) {
|
|
22685
|
-
var arrow = document.getElementById('sort-arrow-' + i);
|
|
22686
|
-
if (arrow) arrow.textContent = i === col ? (_sortAsc ? ' \\u25B2' : ' \\u25BC') : '';
|
|
22687
|
-
}
|
|
22688
|
-
|
|
22689
|
-
// Group rows: root rows + their child/contribution rows
|
|
22690
|
-
var groups = [];
|
|
22691
|
-
var current = null;
|
|
22692
|
-
for (var r = 0; r < allRows.length; r++) {
|
|
22693
|
-
var row = allRows[r];
|
|
22694
|
-
var isChild = row.classList.contains('child-row') || row.classList.contains('contribution-row');
|
|
22695
|
-
if (!isChild) {
|
|
22696
|
-
current = { root: row, children: [] };
|
|
22697
|
-
groups.push(current);
|
|
22698
|
-
} else if (current) {
|
|
22699
|
-
current.children.push(row);
|
|
22700
|
-
}
|
|
22701
|
-
}
|
|
22702
|
-
|
|
22703
|
-
// Sort groups by root row text content of target column
|
|
22704
|
-
groups.sort(function(a, b) {
|
|
22705
|
-
var aText = (a.root.children[col] ? a.root.children[col].textContent : '').trim().toLowerCase();
|
|
22706
|
-
var bText = (b.root.children[col] ? b.root.children[col].textContent : '').trim().toLowerCase();
|
|
22707
|
-
if (aText < bText) return _sortAsc ? -1 : 1;
|
|
22708
|
-
if (aText > bText) return _sortAsc ? 1 : -1;
|
|
22709
|
-
return 0;
|
|
22710
|
-
});
|
|
22711
|
-
|
|
22712
|
-
// Re-append rows in sorted order
|
|
22713
|
-
for (var g = 0; g < groups.length; g++) {
|
|
22714
|
-
tbody.appendChild(groups[g].root);
|
|
22715
|
-
for (var c = 0; c < groups[g].children.length; c++) {
|
|
22716
|
-
tbody.appendChild(groups[g].children[c]);
|
|
22717
|
-
}
|
|
22718
|
-
}
|
|
22719
|
-
}
|
|
22720
|
-
|
|
22721
22775
|
async function generateSummary() {
|
|
22722
22776
|
var btn = document.getElementById('generate-btn');
|
|
22723
22777
|
var loading = document.getElementById('summary-loading');
|
|
@@ -23508,7 +23562,7 @@ function tlSprintPage(ctx) {
|
|
|
23508
23562
|
`<div class="table-wrap">
|
|
23509
23563
|
<table>
|
|
23510
23564
|
<thead>
|
|
23511
|
-
<tr><th>ID</th><th>Title</th><th>Type</th><th>Status</th><th>
|
|
23565
|
+
<tr><th>ID</th><th>Title</th><th>Type</th><th>Status</th><th>Focus</th></tr>
|
|
23512
23566
|
</thead>
|
|
23513
23567
|
<tbody>
|
|
23514
23568
|
${techItems.map((w) => `
|
|
@@ -23517,7 +23571,7 @@ function tlSprintPage(ctx) {
|
|
|
23517
23571
|
<td>${escapeHtml(w.title)}</td>
|
|
23518
23572
|
<td>${escapeHtml(typeLabel(w.type))}</td>
|
|
23519
23573
|
<td>${statusBadge(w.status)}</td>
|
|
23520
|
-
<td>${w.
|
|
23574
|
+
<td>${w.workFocus ? `<span class="badge badge-subtle">${escapeHtml(w.workFocus)}</span>` : '<span class="text-dim">\u2014</span>'}</td>
|
|
23521
23575
|
</tr>`).join("")}
|
|
23522
23576
|
</tbody>
|
|
23523
23577
|
</table>
|
|
@@ -25655,6 +25709,556 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
25655
25709
|
];
|
|
25656
25710
|
}
|
|
25657
25711
|
|
|
25712
|
+
// src/agent/tools/doctor.ts
|
|
25713
|
+
import { tool as tool23 } from "@anthropic-ai/claude-agent-sdk";
|
|
25714
|
+
|
|
25715
|
+
// src/doctor/rules/tag-migration.ts
|
|
25716
|
+
var RULE_ID = "tag-migration";
|
|
25717
|
+
var RULE_NAME = "Tag Migration";
|
|
25718
|
+
var tagMigrationRule = {
|
|
25719
|
+
id: RULE_ID,
|
|
25720
|
+
name: RULE_NAME,
|
|
25721
|
+
description: "Detects deprecated stream:* tags and replaces them with focus:*",
|
|
25722
|
+
scan(ctx) {
|
|
25723
|
+
const issues = [];
|
|
25724
|
+
for (const doc of ctx.allDocuments) {
|
|
25725
|
+
const tags = doc.frontmatter.tags;
|
|
25726
|
+
if (!Array.isArray(tags)) continue;
|
|
25727
|
+
const streamTags = tags.filter((t) => t.startsWith("stream:"));
|
|
25728
|
+
for (const tag of streamTags) {
|
|
25729
|
+
issues.push({
|
|
25730
|
+
ruleId: RULE_ID,
|
|
25731
|
+
ruleName: RULE_NAME,
|
|
25732
|
+
documentId: doc.frontmatter.id,
|
|
25733
|
+
filePath: doc.filePath,
|
|
25734
|
+
documentType: doc.frontmatter.type,
|
|
25735
|
+
message: `Deprecated tag "${tag}" should be "${tag.replace("stream:", "focus:")}"`,
|
|
25736
|
+
severity: "warning",
|
|
25737
|
+
fixable: true
|
|
25738
|
+
});
|
|
25739
|
+
}
|
|
25740
|
+
}
|
|
25741
|
+
return issues;
|
|
25742
|
+
},
|
|
25743
|
+
fix(ctx) {
|
|
25744
|
+
const fixes = [];
|
|
25745
|
+
for (const doc of ctx.allDocuments) {
|
|
25746
|
+
const tags = doc.frontmatter.tags;
|
|
25747
|
+
if (!Array.isArray(tags)) continue;
|
|
25748
|
+
const streamTags = tags.filter((t) => t.startsWith("stream:"));
|
|
25749
|
+
if (streamTags.length === 0) continue;
|
|
25750
|
+
const newTags = tags.map(
|
|
25751
|
+
(t) => t.startsWith("stream:") ? t.replace("stream:", "focus:") : t
|
|
25752
|
+
);
|
|
25753
|
+
ctx.store.update(doc.frontmatter.id, { tags: newTags });
|
|
25754
|
+
for (const tag of streamTags) {
|
|
25755
|
+
fixes.push({
|
|
25756
|
+
issue: {
|
|
25757
|
+
ruleId: RULE_ID,
|
|
25758
|
+
ruleName: RULE_NAME,
|
|
25759
|
+
documentId: doc.frontmatter.id,
|
|
25760
|
+
filePath: doc.filePath,
|
|
25761
|
+
documentType: doc.frontmatter.type,
|
|
25762
|
+
message: `Deprecated tag "${tag}" should be "${tag.replace("stream:", "focus:")}"`,
|
|
25763
|
+
severity: "warning",
|
|
25764
|
+
fixable: true
|
|
25765
|
+
},
|
|
25766
|
+
fixDescription: `Renamed "${tag}" to "${tag.replace("stream:", "focus:")}"`
|
|
25767
|
+
});
|
|
25768
|
+
}
|
|
25769
|
+
}
|
|
25770
|
+
return fixes;
|
|
25771
|
+
}
|
|
25772
|
+
};
|
|
25773
|
+
|
|
25774
|
+
// src/doctor/rules/array-normalization.ts
|
|
25775
|
+
var RULE_ID2 = "array-normalization";
|
|
25776
|
+
var RULE_NAME2 = "Array Normalization";
|
|
25777
|
+
var FIELDS = [
|
|
25778
|
+
{
|
|
25779
|
+
field: "linkedEpic",
|
|
25780
|
+
aliases: ["linkedEpics"],
|
|
25781
|
+
normalize: normalizeLinkedEpics
|
|
25782
|
+
},
|
|
25783
|
+
{
|
|
25784
|
+
field: "linkedFeature",
|
|
25785
|
+
aliases: ["linkedFeatures"],
|
|
25786
|
+
normalize: normalizeLinkedFeatures
|
|
25787
|
+
}
|
|
25788
|
+
];
|
|
25789
|
+
var arrayNormalizationRule = {
|
|
25790
|
+
id: RULE_ID2,
|
|
25791
|
+
name: RULE_NAME2,
|
|
25792
|
+
description: "Normalizes linkedEpic/linkedFeature from strings to arrays and resolves field aliases",
|
|
25793
|
+
scan(ctx) {
|
|
25794
|
+
const issues = [];
|
|
25795
|
+
for (const doc of ctx.allDocuments) {
|
|
25796
|
+
const fm = doc.frontmatter;
|
|
25797
|
+
for (const cfg of FIELDS) {
|
|
25798
|
+
for (const alias of cfg.aliases) {
|
|
25799
|
+
if (fm[alias] !== void 0) {
|
|
25800
|
+
issues.push({
|
|
25801
|
+
ruleId: RULE_ID2,
|
|
25802
|
+
ruleName: RULE_NAME2,
|
|
25803
|
+
documentId: doc.frontmatter.id,
|
|
25804
|
+
filePath: doc.filePath,
|
|
25805
|
+
documentType: doc.frontmatter.type,
|
|
25806
|
+
message: `Field "${alias}" should be renamed to "${cfg.field}"`,
|
|
25807
|
+
severity: "warning",
|
|
25808
|
+
fixable: true
|
|
25809
|
+
});
|
|
25810
|
+
}
|
|
25811
|
+
}
|
|
25812
|
+
const value = fm[cfg.field];
|
|
25813
|
+
if (typeof value === "string") {
|
|
25814
|
+
issues.push({
|
|
25815
|
+
ruleId: RULE_ID2,
|
|
25816
|
+
ruleName: RULE_NAME2,
|
|
25817
|
+
documentId: doc.frontmatter.id,
|
|
25818
|
+
filePath: doc.filePath,
|
|
25819
|
+
documentType: doc.frontmatter.type,
|
|
25820
|
+
message: `Field "${cfg.field}" is a string ("${value}") but should be an array`,
|
|
25821
|
+
severity: "warning",
|
|
25822
|
+
fixable: true
|
|
25823
|
+
});
|
|
25824
|
+
}
|
|
25825
|
+
}
|
|
25826
|
+
}
|
|
25827
|
+
return issues;
|
|
25828
|
+
},
|
|
25829
|
+
fix(ctx) {
|
|
25830
|
+
const fixes = [];
|
|
25831
|
+
for (const doc of ctx.allDocuments) {
|
|
25832
|
+
const fm = doc.frontmatter;
|
|
25833
|
+
const updates = {};
|
|
25834
|
+
let needsUpdate = false;
|
|
25835
|
+
for (const cfg of FIELDS) {
|
|
25836
|
+
for (const alias of cfg.aliases) {
|
|
25837
|
+
if (fm[alias] !== void 0) {
|
|
25838
|
+
const aliasValues = cfg.normalize(fm[alias]);
|
|
25839
|
+
const existing = cfg.normalize(fm[cfg.field]);
|
|
25840
|
+
const merged = [.../* @__PURE__ */ new Set([...existing, ...aliasValues])];
|
|
25841
|
+
updates[cfg.field] = merged;
|
|
25842
|
+
updates[alias] = void 0;
|
|
25843
|
+
needsUpdate = true;
|
|
25844
|
+
fixes.push({
|
|
25845
|
+
issue: {
|
|
25846
|
+
ruleId: RULE_ID2,
|
|
25847
|
+
ruleName: RULE_NAME2,
|
|
25848
|
+
documentId: doc.frontmatter.id,
|
|
25849
|
+
filePath: doc.filePath,
|
|
25850
|
+
documentType: doc.frontmatter.type,
|
|
25851
|
+
message: `Field "${alias}" should be renamed to "${cfg.field}"`,
|
|
25852
|
+
severity: "warning",
|
|
25853
|
+
fixable: true
|
|
25854
|
+
},
|
|
25855
|
+
fixDescription: `Merged "${alias}" into "${cfg.field}" and removed alias`
|
|
25856
|
+
});
|
|
25857
|
+
}
|
|
25858
|
+
}
|
|
25859
|
+
const value = updates[cfg.field] ?? fm[cfg.field];
|
|
25860
|
+
if (typeof value === "string") {
|
|
25861
|
+
updates[cfg.field] = cfg.normalize(value);
|
|
25862
|
+
needsUpdate = true;
|
|
25863
|
+
fixes.push({
|
|
25864
|
+
issue: {
|
|
25865
|
+
ruleId: RULE_ID2,
|
|
25866
|
+
ruleName: RULE_NAME2,
|
|
25867
|
+
documentId: doc.frontmatter.id,
|
|
25868
|
+
filePath: doc.filePath,
|
|
25869
|
+
documentType: doc.frontmatter.type,
|
|
25870
|
+
message: `Field "${cfg.field}" is a string ("${value}") but should be an array`,
|
|
25871
|
+
severity: "warning",
|
|
25872
|
+
fixable: true
|
|
25873
|
+
},
|
|
25874
|
+
fixDescription: `Normalized "${cfg.field}" from string to array`
|
|
25875
|
+
});
|
|
25876
|
+
}
|
|
25877
|
+
}
|
|
25878
|
+
if (needsUpdate) {
|
|
25879
|
+
ctx.store.update(doc.frontmatter.id, updates);
|
|
25880
|
+
}
|
|
25881
|
+
}
|
|
25882
|
+
return fixes;
|
|
25883
|
+
}
|
|
25884
|
+
};
|
|
25885
|
+
|
|
25886
|
+
// src/doctor/rules/missing-auto-tags.ts
|
|
25887
|
+
var RULE_ID3 = "missing-auto-tags";
|
|
25888
|
+
var RULE_NAME3 = "Missing Auto Tags";
|
|
25889
|
+
var missingAutoTagsRule = {
|
|
25890
|
+
id: RULE_ID3,
|
|
25891
|
+
name: RULE_NAME3,
|
|
25892
|
+
description: "Ensures tasks have epic:E-xxx tags for their linkedEpic and epics have feature:F-xxx tags",
|
|
25893
|
+
scan(ctx) {
|
|
25894
|
+
const issues = [];
|
|
25895
|
+
for (const doc of ctx.allDocuments) {
|
|
25896
|
+
const fm = doc.frontmatter;
|
|
25897
|
+
const tags = doc.frontmatter.tags ?? [];
|
|
25898
|
+
const linkedEpics = normalizeLinkedEpics(fm.linkedEpic);
|
|
25899
|
+
if (linkedEpics.length > 0) {
|
|
25900
|
+
const expected = generateEpicTags(linkedEpics);
|
|
25901
|
+
const missing = expected.filter((t) => !tags.includes(t));
|
|
25902
|
+
for (const tag of missing) {
|
|
25903
|
+
issues.push({
|
|
25904
|
+
ruleId: RULE_ID3,
|
|
25905
|
+
ruleName: RULE_NAME3,
|
|
25906
|
+
documentId: doc.frontmatter.id,
|
|
25907
|
+
filePath: doc.filePath,
|
|
25908
|
+
documentType: doc.frontmatter.type,
|
|
25909
|
+
message: `Missing auto-tag "${tag}" for linkedEpic`,
|
|
25910
|
+
severity: "warning",
|
|
25911
|
+
fixable: true
|
|
25912
|
+
});
|
|
25913
|
+
}
|
|
25914
|
+
}
|
|
25915
|
+
const linkedFeatures = normalizeLinkedFeatures(fm.linkedFeature);
|
|
25916
|
+
if (linkedFeatures.length > 0) {
|
|
25917
|
+
const expected = generateFeatureTags(linkedFeatures);
|
|
25918
|
+
const missing = expected.filter((t) => !tags.includes(t));
|
|
25919
|
+
for (const tag of missing) {
|
|
25920
|
+
issues.push({
|
|
25921
|
+
ruleId: RULE_ID3,
|
|
25922
|
+
ruleName: RULE_NAME3,
|
|
25923
|
+
documentId: doc.frontmatter.id,
|
|
25924
|
+
filePath: doc.filePath,
|
|
25925
|
+
documentType: doc.frontmatter.type,
|
|
25926
|
+
message: `Missing auto-tag "${tag}" for linkedFeature`,
|
|
25927
|
+
severity: "warning",
|
|
25928
|
+
fixable: true
|
|
25929
|
+
});
|
|
25930
|
+
}
|
|
25931
|
+
}
|
|
25932
|
+
}
|
|
25933
|
+
return issues;
|
|
25934
|
+
},
|
|
25935
|
+
fix(ctx) {
|
|
25936
|
+
const fixes = [];
|
|
25937
|
+
for (const doc of ctx.allDocuments) {
|
|
25938
|
+
const fm = doc.frontmatter;
|
|
25939
|
+
const tags = [...doc.frontmatter.tags ?? []];
|
|
25940
|
+
let changed = false;
|
|
25941
|
+
const linkedEpics = normalizeLinkedEpics(fm.linkedEpic);
|
|
25942
|
+
if (linkedEpics.length > 0) {
|
|
25943
|
+
const expected = generateEpicTags(linkedEpics);
|
|
25944
|
+
for (const tag of expected) {
|
|
25945
|
+
if (!tags.includes(tag)) {
|
|
25946
|
+
tags.push(tag);
|
|
25947
|
+
changed = true;
|
|
25948
|
+
fixes.push({
|
|
25949
|
+
issue: {
|
|
25950
|
+
ruleId: RULE_ID3,
|
|
25951
|
+
ruleName: RULE_NAME3,
|
|
25952
|
+
documentId: doc.frontmatter.id,
|
|
25953
|
+
filePath: doc.filePath,
|
|
25954
|
+
documentType: doc.frontmatter.type,
|
|
25955
|
+
message: `Missing auto-tag "${tag}" for linkedEpic`,
|
|
25956
|
+
severity: "warning",
|
|
25957
|
+
fixable: true
|
|
25958
|
+
},
|
|
25959
|
+
fixDescription: `Added tag "${tag}"`
|
|
25960
|
+
});
|
|
25961
|
+
}
|
|
25962
|
+
}
|
|
25963
|
+
}
|
|
25964
|
+
const linkedFeatures = normalizeLinkedFeatures(fm.linkedFeature);
|
|
25965
|
+
if (linkedFeatures.length > 0) {
|
|
25966
|
+
const expected = generateFeatureTags(linkedFeatures);
|
|
25967
|
+
for (const tag of expected) {
|
|
25968
|
+
if (!tags.includes(tag)) {
|
|
25969
|
+
tags.push(tag);
|
|
25970
|
+
changed = true;
|
|
25971
|
+
fixes.push({
|
|
25972
|
+
issue: {
|
|
25973
|
+
ruleId: RULE_ID3,
|
|
25974
|
+
ruleName: RULE_NAME3,
|
|
25975
|
+
documentId: doc.frontmatter.id,
|
|
25976
|
+
filePath: doc.filePath,
|
|
25977
|
+
documentType: doc.frontmatter.type,
|
|
25978
|
+
message: `Missing auto-tag "${tag}" for linkedFeature`,
|
|
25979
|
+
severity: "warning",
|
|
25980
|
+
fixable: true
|
|
25981
|
+
},
|
|
25982
|
+
fixDescription: `Added tag "${tag}"`
|
|
25983
|
+
});
|
|
25984
|
+
}
|
|
25985
|
+
}
|
|
25986
|
+
}
|
|
25987
|
+
if (changed) {
|
|
25988
|
+
ctx.store.update(doc.frontmatter.id, { tags });
|
|
25989
|
+
}
|
|
25990
|
+
}
|
|
25991
|
+
return fixes;
|
|
25992
|
+
}
|
|
25993
|
+
};
|
|
25994
|
+
|
|
25995
|
+
// src/doctor/rules/progress-consistency.ts
|
|
25996
|
+
var RULE_ID4 = "progress-consistency";
|
|
25997
|
+
var RULE_NAME4 = "Progress Consistency";
|
|
25998
|
+
var progressConsistencyRule = {
|
|
25999
|
+
id: RULE_ID4,
|
|
26000
|
+
name: RULE_NAME4,
|
|
26001
|
+
description: "Detects done-status documents with progress != 100 and progressOverride:true without a progress value",
|
|
26002
|
+
scan(ctx) {
|
|
26003
|
+
const issues = [];
|
|
26004
|
+
for (const doc of ctx.allDocuments) {
|
|
26005
|
+
const fm = doc.frontmatter;
|
|
26006
|
+
const status = doc.frontmatter.status;
|
|
26007
|
+
const progress = fm.progress;
|
|
26008
|
+
const progressOverride = fm.progressOverride;
|
|
26009
|
+
if (status === "done" && progress !== void 0 && progress !== 100) {
|
|
26010
|
+
issues.push({
|
|
26011
|
+
ruleId: RULE_ID4,
|
|
26012
|
+
ruleName: RULE_NAME4,
|
|
26013
|
+
documentId: doc.frontmatter.id,
|
|
26014
|
+
filePath: doc.filePath,
|
|
26015
|
+
documentType: doc.frontmatter.type,
|
|
26016
|
+
message: `Status is "done" but progress is ${progress} (expected 100)`,
|
|
26017
|
+
severity: "error",
|
|
26018
|
+
fixable: true
|
|
26019
|
+
});
|
|
26020
|
+
}
|
|
26021
|
+
if (progressOverride === true && progress === void 0) {
|
|
26022
|
+
issues.push({
|
|
26023
|
+
ruleId: RULE_ID4,
|
|
26024
|
+
ruleName: RULE_NAME4,
|
|
26025
|
+
documentId: doc.frontmatter.id,
|
|
26026
|
+
filePath: doc.filePath,
|
|
26027
|
+
documentType: doc.frontmatter.type,
|
|
26028
|
+
message: `progressOverride is true but no progress value is set`,
|
|
26029
|
+
severity: "warning",
|
|
26030
|
+
fixable: true
|
|
26031
|
+
});
|
|
26032
|
+
}
|
|
26033
|
+
}
|
|
26034
|
+
return issues;
|
|
26035
|
+
},
|
|
26036
|
+
fix(ctx) {
|
|
26037
|
+
const fixes = [];
|
|
26038
|
+
for (const doc of ctx.allDocuments) {
|
|
26039
|
+
const fm = doc.frontmatter;
|
|
26040
|
+
const status = doc.frontmatter.status;
|
|
26041
|
+
const progress = fm.progress;
|
|
26042
|
+
const progressOverride = fm.progressOverride;
|
|
26043
|
+
if (status === "done" && progress !== void 0 && progress !== 100) {
|
|
26044
|
+
ctx.store.update(doc.frontmatter.id, { progress: 100 });
|
|
26045
|
+
fixes.push({
|
|
26046
|
+
issue: {
|
|
26047
|
+
ruleId: RULE_ID4,
|
|
26048
|
+
ruleName: RULE_NAME4,
|
|
26049
|
+
documentId: doc.frontmatter.id,
|
|
26050
|
+
filePath: doc.filePath,
|
|
26051
|
+
documentType: doc.frontmatter.type,
|
|
26052
|
+
message: `Status is "done" but progress is ${progress} (expected 100)`,
|
|
26053
|
+
severity: "error",
|
|
26054
|
+
fixable: true
|
|
26055
|
+
},
|
|
26056
|
+
fixDescription: `Set progress to 100`
|
|
26057
|
+
});
|
|
26058
|
+
}
|
|
26059
|
+
if (progressOverride === true && progress === void 0) {
|
|
26060
|
+
ctx.store.update(doc.frontmatter.id, { progressOverride: false });
|
|
26061
|
+
fixes.push({
|
|
26062
|
+
issue: {
|
|
26063
|
+
ruleId: RULE_ID4,
|
|
26064
|
+
ruleName: RULE_NAME4,
|
|
26065
|
+
documentId: doc.frontmatter.id,
|
|
26066
|
+
filePath: doc.filePath,
|
|
26067
|
+
documentType: doc.frontmatter.type,
|
|
26068
|
+
message: `progressOverride is true but no progress value is set`,
|
|
26069
|
+
severity: "warning",
|
|
26070
|
+
fixable: true
|
|
26071
|
+
},
|
|
26072
|
+
fixDescription: `Set progressOverride to false`
|
|
26073
|
+
});
|
|
26074
|
+
}
|
|
26075
|
+
}
|
|
26076
|
+
return fixes;
|
|
26077
|
+
}
|
|
26078
|
+
};
|
|
26079
|
+
|
|
26080
|
+
// src/doctor/rules/orphaned-references.ts
|
|
26081
|
+
var RULE_ID5 = "orphaned-references";
|
|
26082
|
+
var RULE_NAME5 = "Orphaned References";
|
|
26083
|
+
var REFERENCE_FIELDS = ["aboutArtifact", "linkedEpic", "linkedFeature"];
|
|
26084
|
+
var orphanedReferencesRule = {
|
|
26085
|
+
id: RULE_ID5,
|
|
26086
|
+
name: RULE_NAME5,
|
|
26087
|
+
description: "Detects references (aboutArtifact, linkedEpic, linkedFeature) pointing to non-existent documents",
|
|
26088
|
+
scan(ctx) {
|
|
26089
|
+
const issues = [];
|
|
26090
|
+
for (const doc of ctx.allDocuments) {
|
|
26091
|
+
const fm = doc.frontmatter;
|
|
26092
|
+
for (const field of REFERENCE_FIELDS) {
|
|
26093
|
+
const value = fm[field];
|
|
26094
|
+
if (value === void 0 || value === null) continue;
|
|
26095
|
+
const refs = Array.isArray(value) ? value.filter((v) => typeof v === "string") : typeof value === "string" ? [value] : [];
|
|
26096
|
+
for (const ref of refs) {
|
|
26097
|
+
if (!ctx.documentIndex.has(ref)) {
|
|
26098
|
+
issues.push({
|
|
26099
|
+
ruleId: RULE_ID5,
|
|
26100
|
+
ruleName: RULE_NAME5,
|
|
26101
|
+
documentId: doc.frontmatter.id,
|
|
26102
|
+
filePath: doc.filePath,
|
|
26103
|
+
documentType: doc.frontmatter.type,
|
|
26104
|
+
message: `Field "${field}" references "${ref}" which does not exist`,
|
|
26105
|
+
severity: "warning",
|
|
26106
|
+
fixable: false
|
|
26107
|
+
});
|
|
26108
|
+
}
|
|
26109
|
+
}
|
|
26110
|
+
}
|
|
26111
|
+
}
|
|
26112
|
+
return issues;
|
|
26113
|
+
},
|
|
26114
|
+
fix() {
|
|
26115
|
+
return [];
|
|
26116
|
+
}
|
|
26117
|
+
};
|
|
26118
|
+
|
|
26119
|
+
// src/doctor/rules/owner-role.ts
|
|
26120
|
+
var RULE_ID6 = "owner-role";
|
|
26121
|
+
var RULE_NAME6 = "Owner Role";
|
|
26122
|
+
var ownerRoleRule = {
|
|
26123
|
+
id: RULE_ID6,
|
|
26124
|
+
name: RULE_NAME6,
|
|
26125
|
+
description: `Detects owner values that are not valid persona roles (${OWNER_SHORT.join(", ")})`,
|
|
26126
|
+
scan(ctx) {
|
|
26127
|
+
const issues = [];
|
|
26128
|
+
for (const doc of ctx.allDocuments) {
|
|
26129
|
+
const owner = doc.frontmatter.owner;
|
|
26130
|
+
if (owner === void 0 || owner === null || owner === "") continue;
|
|
26131
|
+
if (!isValidOwner(owner)) {
|
|
26132
|
+
issues.push({
|
|
26133
|
+
ruleId: RULE_ID6,
|
|
26134
|
+
ruleName: RULE_NAME6,
|
|
26135
|
+
documentId: doc.frontmatter.id,
|
|
26136
|
+
filePath: doc.filePath,
|
|
26137
|
+
documentType: doc.frontmatter.type,
|
|
26138
|
+
message: `Owner "${owner}" is not a valid persona role. Expected one of: ${OWNER_SHORT.join(", ")}`,
|
|
26139
|
+
severity: "warning",
|
|
26140
|
+
fixable: false
|
|
26141
|
+
});
|
|
26142
|
+
}
|
|
26143
|
+
}
|
|
26144
|
+
return issues;
|
|
26145
|
+
},
|
|
26146
|
+
fix() {
|
|
26147
|
+
return [];
|
|
26148
|
+
}
|
|
26149
|
+
};
|
|
26150
|
+
|
|
26151
|
+
// src/doctor/rules/index.ts
|
|
26152
|
+
var allRules = [
|
|
26153
|
+
tagMigrationRule,
|
|
26154
|
+
arrayNormalizationRule,
|
|
26155
|
+
missingAutoTagsRule,
|
|
26156
|
+
progressConsistencyRule,
|
|
26157
|
+
orphanedReferencesRule,
|
|
26158
|
+
ownerRoleRule
|
|
26159
|
+
];
|
|
26160
|
+
|
|
26161
|
+
// src/doctor/engine.ts
|
|
26162
|
+
function buildDoctorContext(store) {
|
|
26163
|
+
const allDocuments = store.list();
|
|
26164
|
+
const documentIndex = new Map(
|
|
26165
|
+
allDocuments.map((doc) => [doc.frontmatter.id, doc])
|
|
26166
|
+
);
|
|
26167
|
+
return { store, allDocuments, documentIndex };
|
|
26168
|
+
}
|
|
26169
|
+
function runDoctorScan(store, ruleFilter) {
|
|
26170
|
+
const rules = resolveRules(ruleFilter);
|
|
26171
|
+
const ctx = buildDoctorContext(store);
|
|
26172
|
+
const issues = rules.flatMap((rule) => rule.scan(ctx));
|
|
26173
|
+
return buildReport(ctx, issues, []);
|
|
26174
|
+
}
|
|
26175
|
+
function runDoctorFix(store, ruleFilter) {
|
|
26176
|
+
const rules = resolveRules(ruleFilter);
|
|
26177
|
+
let ctx = buildDoctorContext(store);
|
|
26178
|
+
const allIssues = rules.flatMap((rule) => rule.scan(ctx));
|
|
26179
|
+
const allFixes = [];
|
|
26180
|
+
for (const rule of rules) {
|
|
26181
|
+
const fixes = rule.fix(ctx);
|
|
26182
|
+
allFixes.push(...fixes);
|
|
26183
|
+
if (fixes.length > 0) {
|
|
26184
|
+
ctx = buildDoctorContext(store);
|
|
26185
|
+
}
|
|
26186
|
+
}
|
|
26187
|
+
return buildReport(ctx, allIssues, allFixes);
|
|
26188
|
+
}
|
|
26189
|
+
function resolveRules(ruleFilter) {
|
|
26190
|
+
if (!ruleFilter) return allRules;
|
|
26191
|
+
const rule = allRules.find((r) => r.id === ruleFilter);
|
|
26192
|
+
if (!rule) {
|
|
26193
|
+
throw new Error(
|
|
26194
|
+
`Unknown rule: ${ruleFilter}. Available: ${allRules.map((r) => r.id).join(", ")}`
|
|
26195
|
+
);
|
|
26196
|
+
}
|
|
26197
|
+
return [rule];
|
|
26198
|
+
}
|
|
26199
|
+
function buildReport(ctx, issues, fixes) {
|
|
26200
|
+
const byRule = {};
|
|
26201
|
+
const bySeverity = { error: 0, warning: 0, info: 0 };
|
|
26202
|
+
let fixableIssues = 0;
|
|
26203
|
+
for (const issue2 of issues) {
|
|
26204
|
+
byRule[issue2.ruleId] = (byRule[issue2.ruleId] ?? 0) + 1;
|
|
26205
|
+
bySeverity[issue2.severity] = (bySeverity[issue2.severity] ?? 0) + 1;
|
|
26206
|
+
if (issue2.fixable) fixableIssues++;
|
|
26207
|
+
}
|
|
26208
|
+
return {
|
|
26209
|
+
scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
26210
|
+
totalDocuments: ctx.allDocuments.length,
|
|
26211
|
+
issues,
|
|
26212
|
+
fixes,
|
|
26213
|
+
summary: {
|
|
26214
|
+
totalIssues: issues.length,
|
|
26215
|
+
fixableIssues,
|
|
26216
|
+
fixedIssues: fixes.length,
|
|
26217
|
+
byRule,
|
|
26218
|
+
bySeverity
|
|
26219
|
+
}
|
|
26220
|
+
};
|
|
26221
|
+
}
|
|
26222
|
+
|
|
26223
|
+
// src/agent/tools/doctor.ts
|
|
26224
|
+
function createDoctorTools(store) {
|
|
26225
|
+
return [
|
|
26226
|
+
tool23(
|
|
26227
|
+
"run_doctor",
|
|
26228
|
+
"Scan project documents for structural issues and optionally auto-repair them. Returns a JSON report with all issues found and fixes applied.",
|
|
26229
|
+
{
|
|
26230
|
+
fix: external_exports.boolean().optional().default(false).describe("When true, auto-repair fixable issues"),
|
|
26231
|
+
rule: external_exports.string().optional().describe(
|
|
26232
|
+
"Run only a specific rule (e.g. tag-migration, array-normalization, missing-auto-tags, progress-consistency, orphaned-references)"
|
|
26233
|
+
)
|
|
26234
|
+
},
|
|
26235
|
+
async (args) => {
|
|
26236
|
+
try {
|
|
26237
|
+
const report = args.fix ? runDoctorFix(store, args.rule) : runDoctorScan(store, args.rule);
|
|
26238
|
+
return {
|
|
26239
|
+
content: [
|
|
26240
|
+
{
|
|
26241
|
+
type: "text",
|
|
26242
|
+
text: JSON.stringify(report, null, 2)
|
|
26243
|
+
}
|
|
26244
|
+
]
|
|
26245
|
+
};
|
|
26246
|
+
} catch (err) {
|
|
26247
|
+
return {
|
|
26248
|
+
content: [
|
|
26249
|
+
{
|
|
26250
|
+
type: "text",
|
|
26251
|
+
text: `Doctor error: ${err instanceof Error ? err.message : String(err)}`
|
|
26252
|
+
}
|
|
26253
|
+
],
|
|
26254
|
+
isError: true
|
|
26255
|
+
};
|
|
26256
|
+
}
|
|
26257
|
+
}
|
|
26258
|
+
)
|
|
26259
|
+
];
|
|
26260
|
+
}
|
|
26261
|
+
|
|
25658
26262
|
// src/agent/mcp-server.ts
|
|
25659
26263
|
function createMarvinMcpServer(store, options) {
|
|
25660
26264
|
const tools = [
|
|
@@ -25666,7 +26270,8 @@ function createMarvinMcpServer(store, options) {
|
|
|
25666
26270
|
...options?.sessionStore ? createSessionTools(options.sessionStore) : [],
|
|
25667
26271
|
...options?.pluginTools ?? [],
|
|
25668
26272
|
...options?.skillTools ?? [],
|
|
25669
|
-
...options?.projectName && options?.navGroups ? createWebTools(store, options.projectName, options.navGroups) : []
|
|
26273
|
+
...options?.projectName && options?.navGroups ? createWebTools(store, options.projectName, options.navGroups) : [],
|
|
26274
|
+
...createDoctorTools(store)
|
|
25670
26275
|
];
|
|
25671
26276
|
return createSdkMcpServer({
|
|
25672
26277
|
name: "marvin-governance",
|
|
@@ -26977,7 +27582,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
26977
27582
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
26978
27583
|
|
|
26979
27584
|
// src/skills/action-tools.ts
|
|
26980
|
-
import { tool as
|
|
27585
|
+
import { tool as tool24 } from "@anthropic-ai/claude-agent-sdk";
|
|
26981
27586
|
|
|
26982
27587
|
// src/skills/action-runner.ts
|
|
26983
27588
|
import { query as query5 } from "@anthropic-ai/claude-agent-sdk";
|
|
@@ -27043,7 +27648,7 @@ function createSkillActionTools(skills, context) {
|
|
|
27043
27648
|
if (!skill.actions) continue;
|
|
27044
27649
|
for (const action of skill.actions) {
|
|
27045
27650
|
tools.push(
|
|
27046
|
-
|
|
27651
|
+
tool24(
|
|
27047
27652
|
`${skill.id}__${action.id}`,
|
|
27048
27653
|
action.description,
|
|
27049
27654
|
{
|
|
@@ -27135,10 +27740,10 @@ ${lines.join("\n\n")}`;
|
|
|
27135
27740
|
}
|
|
27136
27741
|
|
|
27137
27742
|
// src/mcp/persona-tools.ts
|
|
27138
|
-
import { tool as
|
|
27743
|
+
import { tool as tool25 } from "@anthropic-ai/claude-agent-sdk";
|
|
27139
27744
|
function createPersonaTools(ctx, marvinDir) {
|
|
27140
27745
|
return [
|
|
27141
|
-
|
|
27746
|
+
tool25(
|
|
27142
27747
|
"set_persona",
|
|
27143
27748
|
"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.",
|
|
27144
27749
|
{
|
|
@@ -27168,7 +27773,7 @@ ${summaries}`
|
|
|
27168
27773
|
};
|
|
27169
27774
|
}
|
|
27170
27775
|
),
|
|
27171
|
-
|
|
27776
|
+
tool25(
|
|
27172
27777
|
"get_persona_guidance",
|
|
27173
27778
|
"Get guidance for a persona without changing the active persona. If no persona is specified, lists all available personas with summaries.",
|
|
27174
27779
|
{
|
|
@@ -28981,12 +29586,76 @@ async function generateClaudeMdCommand(options) {
|
|
|
28981
29586
|
console.log(chalk18.green("Created .marvin/CLAUDE.md"));
|
|
28982
29587
|
}
|
|
28983
29588
|
|
|
29589
|
+
// src/cli/commands/doctor.ts
|
|
29590
|
+
import chalk19 from "chalk";
|
|
29591
|
+
var SEVERITY_ICONS = {
|
|
29592
|
+
error: chalk19.red("x"),
|
|
29593
|
+
warning: chalk19.yellow("!"),
|
|
29594
|
+
info: chalk19.blue("i")
|
|
29595
|
+
};
|
|
29596
|
+
async function doctorCommand(options) {
|
|
29597
|
+
const project = loadProject();
|
|
29598
|
+
const plugin = resolvePlugin(project.config.methodology);
|
|
29599
|
+
const pluginRegistrations = plugin?.documentTypeRegistrations ?? [];
|
|
29600
|
+
const allSkills = loadAllSkills(project.marvinDir);
|
|
29601
|
+
const allSkillIds = [...allSkills.keys()];
|
|
29602
|
+
const skillRegistrations = collectSkillRegistrations(allSkillIds, allSkills);
|
|
29603
|
+
const store = new DocumentStore(project.marvinDir, [
|
|
29604
|
+
...pluginRegistrations,
|
|
29605
|
+
...skillRegistrations
|
|
29606
|
+
]);
|
|
29607
|
+
const report = options.fix ? runDoctorFix(store, options.rule) : runDoctorScan(store, options.rule);
|
|
29608
|
+
printReport(report, !!options.fix);
|
|
29609
|
+
}
|
|
29610
|
+
function printReport(report, didFix) {
|
|
29611
|
+
console.log(chalk19.bold(`
|
|
29612
|
+
Artifact Doctor
|
|
29613
|
+
`));
|
|
29614
|
+
console.log(`Scanned ${report.totalDocuments} documents
|
|
29615
|
+
`);
|
|
29616
|
+
if (report.issues.length === 0) {
|
|
29617
|
+
console.log(chalk19.green("No issues found. All documents are healthy.\n"));
|
|
29618
|
+
return;
|
|
29619
|
+
}
|
|
29620
|
+
const byDoc = /* @__PURE__ */ new Map();
|
|
29621
|
+
for (const issue2 of report.issues) {
|
|
29622
|
+
const key = issue2.documentId;
|
|
29623
|
+
if (!byDoc.has(key)) byDoc.set(key, []);
|
|
29624
|
+
byDoc.get(key).push(issue2);
|
|
29625
|
+
}
|
|
29626
|
+
for (const [docId, issues] of byDoc) {
|
|
29627
|
+
const first = issues[0];
|
|
29628
|
+
console.log(chalk19.cyan(docId) + chalk19.dim(` (${first.documentType})`));
|
|
29629
|
+
for (const issue2 of issues) {
|
|
29630
|
+
const icon = SEVERITY_ICONS[issue2.severity] ?? " ";
|
|
29631
|
+
const fixLabel = issue2.fixable ? chalk19.dim(" [fixable]") : "";
|
|
29632
|
+
console.log(` ${icon} ${issue2.message}${fixLabel}`);
|
|
29633
|
+
}
|
|
29634
|
+
console.log();
|
|
29635
|
+
}
|
|
29636
|
+
console.log(chalk19.underline("Summary"));
|
|
29637
|
+
console.log(` Total issues: ${report.summary.totalIssues}`);
|
|
29638
|
+
console.log(` Fixable: ${report.summary.fixableIssues}`);
|
|
29639
|
+
if (didFix) {
|
|
29640
|
+
console.log(chalk19.green(` Fixed: ${report.summary.fixedIssues}`));
|
|
29641
|
+
}
|
|
29642
|
+
const { bySeverity } = report.summary;
|
|
29643
|
+
if (bySeverity.error > 0) console.log(chalk19.red(` Errors: ${bySeverity.error}`));
|
|
29644
|
+
if (bySeverity.warning > 0) console.log(chalk19.yellow(` Warnings: ${bySeverity.warning}`));
|
|
29645
|
+
if (bySeverity.info > 0) console.log(chalk19.blue(` Info: ${bySeverity.info}`));
|
|
29646
|
+
if (!didFix && report.summary.fixableIssues > 0) {
|
|
29647
|
+
console.log(chalk19.dim(`
|
|
29648
|
+
Run "marvin doctor --fix" to auto-repair fixable issues.`));
|
|
29649
|
+
}
|
|
29650
|
+
console.log();
|
|
29651
|
+
}
|
|
29652
|
+
|
|
28984
29653
|
// src/cli/program.ts
|
|
28985
29654
|
function createProgram() {
|
|
28986
29655
|
const program2 = new Command();
|
|
28987
29656
|
program2.name("marvin").description(
|
|
28988
29657
|
"AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
|
|
28989
|
-
).version("0.5.
|
|
29658
|
+
).version("0.5.3");
|
|
28990
29659
|
program2.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
|
|
28991
29660
|
await initCommand();
|
|
28992
29661
|
});
|
|
@@ -29075,6 +29744,9 @@ function createProgram() {
|
|
|
29075
29744
|
program2.command("web").description("Launch a local web dashboard for project data").option("-p, --port <port>", "Port to listen on (default: 3000)").option("--no-open", "Don't auto-open the browser").action(async (options) => {
|
|
29076
29745
|
await webCommand(options);
|
|
29077
29746
|
});
|
|
29747
|
+
program2.command("doctor").description("Scan project documents for structural issues and optionally auto-repair them").option("--fix", "Auto-repair fixable issues").option("--rule <rule>", "Run only a specific rule").action(async (options) => {
|
|
29748
|
+
await doctorCommand(options);
|
|
29749
|
+
});
|
|
29078
29750
|
const generateCmd = program2.command("generate").description("Generate project files");
|
|
29079
29751
|
generateCmd.command("claude-md").description("Generate .marvin/CLAUDE.md project instruction file").option("--force", "Overwrite existing file without prompting").action(async (options) => {
|
|
29080
29752
|
await generateClaudeMdCommand(options);
|