mrvn-cli 0.5.1 → 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.
@@ -6474,13 +6474,13 @@ var error16 = () => {
6474
6474
  // no unit
6475
6475
  };
6476
6476
  const typeEntry = (t) => t ? TypeNames[t] : void 0;
6477
- const typeLabel4 = (t) => {
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${typeLabel4(t)}`;
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 ?? ""] ?? typeLabel4(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: external_exports.string().optional().describe("Person responsible for this decision"),
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: external_exports.string().optional().describe("New 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: [
@@ -14482,12 +14507,13 @@ function createActionTools(store) {
14482
14507
  title: external_exports.string().describe("Title of the action item"),
14483
14508
  content: external_exports.string().describe("Description of what needs to be done"),
14484
14509
  status: external_exports.string().optional().describe("Status (default: 'open')"),
14485
- owner: external_exports.string().optional().describe("Person responsible"),
14510
+ owner: ownerSchema.optional().describe("Persona role responsible (po, dm, tl)"),
14511
+ assignee: external_exports.string().optional().describe("Person assigned to do the work"),
14486
14512
  priority: external_exports.string().optional().describe("Priority (high, medium, low)"),
14487
14513
  tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization"),
14488
14514
  dueDate: external_exports.string().optional().describe("Due date in ISO format (e.g. '2026-03-15')"),
14489
14515
  sprints: external_exports.array(external_exports.string()).optional().describe("Sprint IDs to assign (e.g. ['SP-001']). Adds sprint:SP-xxx tags."),
14490
- workStream: external_exports.string().optional().describe("Work stream name (e.g. 'Budget UX'). Adds a stream:<value> tag.")
14516
+ workFocus: external_exports.string().optional().describe("Work focus name (e.g. 'Budget UX'). Adds a focus:<value> tag.")
14491
14517
  },
14492
14518
  async (args) => {
14493
14519
  const tags = [...args.tags ?? []];
@@ -14497,15 +14523,16 @@ function createActionTools(store) {
14497
14523
  if (!tags.includes(tag)) tags.push(tag);
14498
14524
  }
14499
14525
  }
14500
- if (args.workStream) {
14501
- tags.push(`stream:${args.workStream}`);
14526
+ if (args.workFocus) {
14527
+ tags.push(`focus:${args.workFocus}`);
14502
14528
  }
14503
14529
  const doc = store.create(
14504
14530
  "action",
14505
14531
  {
14506
14532
  title: args.title,
14507
14533
  status: args.status,
14508
- owner: args.owner,
14534
+ owner: normalizeOwner(args.owner),
14535
+ assignee: args.assignee,
14509
14536
  priority: args.priority,
14510
14537
  tags: tags.length > 0 ? tags : void 0,
14511
14538
  dueDate: args.dueDate
@@ -14533,16 +14560,19 @@ function createActionTools(store) {
14533
14560
  title: external_exports.string().optional().describe("New title"),
14534
14561
  status: external_exports.string().optional().describe("New status"),
14535
14562
  content: external_exports.string().optional().describe("New content"),
14536
- owner: external_exports.string().optional().describe("New 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"),
14537
14565
  priority: external_exports.string().optional().describe("New priority"),
14538
14566
  dueDate: external_exports.string().optional().describe("Due date in ISO format (e.g. '2026-03-15')"),
14539
14567
  tags: external_exports.array(external_exports.string()).optional().describe("Replace all tags. When provided with sprints, sprint tags are merged into this array."),
14540
14568
  sprints: external_exports.array(external_exports.string()).optional().describe("Sprint IDs to assign (replaces existing sprint tags). E.g. ['SP-001']."),
14541
- workStream: external_exports.string().optional().describe("Work stream name (e.g. 'Budget UX'). Replaces existing stream:<value> tag."),
14569
+ workFocus: external_exports.string().optional().describe("Work focus name (e.g. 'Budget UX'). Replaces existing focus:<value> tag."),
14542
14570
  progress: external_exports.number().optional().describe("Explicit progress percentage (0-100).")
14543
14571
  },
14544
14572
  async (args) => {
14545
- const { id, content, sprints, tags, workStream, progress, ...updates } = args;
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;
14546
14576
  if (tags !== void 0) {
14547
14577
  const merged = [...tags];
14548
14578
  if (sprints) {
@@ -14551,14 +14581,14 @@ function createActionTools(store) {
14551
14581
  if (!merged.includes(tag)) merged.push(tag);
14552
14582
  }
14553
14583
  }
14554
- if (workStream !== void 0) {
14555
- const filtered = merged.filter((t) => !t.startsWith("stream:"));
14556
- filtered.push(`stream:${workStream}`);
14584
+ if (workFocus !== void 0) {
14585
+ const filtered = merged.filter((t) => !t.startsWith("focus:"));
14586
+ filtered.push(`focus:${workFocus}`);
14557
14587
  updates.tags = filtered;
14558
14588
  } else {
14559
14589
  updates.tags = merged;
14560
14590
  }
14561
- } else if (sprints !== void 0 || workStream !== void 0) {
14591
+ } else if (sprints !== void 0 || workFocus !== void 0) {
14562
14592
  const existing = store.get(id);
14563
14593
  if (!existing) {
14564
14594
  return {
@@ -14571,9 +14601,9 @@ function createActionTools(store) {
14571
14601
  existingTags = existingTags.filter((t) => !t.startsWith("sprint:"));
14572
14602
  existingTags.push(...sprints.map((s) => `sprint:${s}`));
14573
14603
  }
14574
- if (workStream !== void 0) {
14575
- existingTags = existingTags.filter((t) => !t.startsWith("stream:"));
14576
- existingTags.push(`stream:${workStream}`);
14604
+ if (workFocus !== void 0) {
14605
+ existingTags = existingTags.filter((t) => !t.startsWith("focus:"));
14606
+ existingTags.push(`focus:${workFocus}`);
14577
14607
  }
14578
14608
  updates.tags = existingTags;
14579
14609
  }
@@ -14686,7 +14716,8 @@ function createQuestionTools(store) {
14686
14716
  title: external_exports.string().describe("The question being asked"),
14687
14717
  content: external_exports.string().describe("Context and details about the question"),
14688
14718
  status: external_exports.string().optional().describe("Status (default: 'open')"),
14689
- owner: external_exports.string().optional().describe("Person who should answer this"),
14719
+ owner: ownerSchema.optional().describe("Persona role responsible (po, dm, tl)"),
14720
+ assignee: external_exports.string().optional().describe("Person assigned to do the work"),
14690
14721
  tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization")
14691
14722
  },
14692
14723
  async (args) => {
@@ -14695,7 +14726,8 @@ function createQuestionTools(store) {
14695
14726
  {
14696
14727
  title: args.title,
14697
14728
  status: args.status,
14698
- owner: args.owner,
14729
+ owner: normalizeOwner(args.owner),
14730
+ assignee: args.assignee,
14699
14731
  tags: args.tags
14700
14732
  },
14701
14733
  args.content
@@ -14718,11 +14750,14 @@ function createQuestionTools(store) {
14718
14750
  title: external_exports.string().optional().describe("New title"),
14719
14751
  status: external_exports.string().optional().describe("New status (e.g. 'answered')"),
14720
14752
  content: external_exports.string().optional().describe("Updated content / answer"),
14721
- owner: external_exports.string().optional().describe("New 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"),
14722
14755
  tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove 'risk', add 'risk-mitigated')")
14723
14756
  },
14724
14757
  async (args) => {
14725
- 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;
14726
14761
  const doc = store.update(id, updates, content);
14727
14762
  return {
14728
14763
  content: [
@@ -14747,18 +14782,20 @@ function createDocumentTools(store) {
14747
14782
  {
14748
14783
  type: external_exports.string().optional().describe(`Filter by document type (registered types: ${store.registeredTypes.join(", ")})`),
14749
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)"),
14750
14786
  tag: external_exports.string().optional().describe("Filter by tag"),
14751
- workStream: external_exports.string().optional().describe("Filter by work stream name (matches stream:<value> tag)")
14787
+ workFocus: external_exports.string().optional().describe("Filter by work focus name (matches focus:<value> tag)")
14752
14788
  },
14753
14789
  async (args) => {
14754
14790
  let docs = store.list({
14755
14791
  type: args.type,
14756
14792
  status: args.status,
14793
+ owner: args.owner,
14757
14794
  tag: args.tag
14758
14795
  });
14759
- if (args.workStream) {
14760
- const streamTag = `stream:${args.workStream}`;
14761
- docs = docs.filter((d) => d.frontmatter.tags?.includes(streamTag));
14796
+ if (args.workFocus) {
14797
+ const focusTag = `focus:${args.workFocus}`;
14798
+ docs = docs.filter((d) => d.frontmatter.tags?.includes(focusTag));
14762
14799
  }
14763
14800
  const summary = docs.map((d) => ({
14764
14801
  id: d.frontmatter.id,
@@ -15080,7 +15117,8 @@ function createMeetingTools(store) {
15080
15117
  title: external_exports.string().describe("Title of the meeting"),
15081
15118
  content: external_exports.string().describe("Meeting agenda, notes, or minutes"),
15082
15119
  status: external_exports.string().optional().describe("Status (default: 'scheduled')"),
15083
- owner: external_exports.string().optional().describe("Meeting organizer"),
15120
+ owner: ownerSchema.optional().describe("Persona role responsible (po, dm, tl)"),
15121
+ assignee: external_exports.string().optional().describe("Person assigned to do the work"),
15084
15122
  tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization"),
15085
15123
  attendees: external_exports.array(external_exports.string()).optional().describe("List of attendees"),
15086
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.")
@@ -15090,7 +15128,8 @@ function createMeetingTools(store) {
15090
15128
  title: args.title,
15091
15129
  status: args.status ?? "scheduled"
15092
15130
  };
15093
- if (args.owner) frontmatter.owner = args.owner;
15131
+ if (args.owner) frontmatter.owner = normalizeOwner(args.owner);
15132
+ if (args.assignee) frontmatter.assignee = args.assignee;
15094
15133
  if (args.tags) frontmatter.tags = args.tags;
15095
15134
  if (args.attendees) frontmatter.attendees = args.attendees;
15096
15135
  frontmatter.date = args.date;
@@ -15117,10 +15156,13 @@ function createMeetingTools(store) {
15117
15156
  title: external_exports.string().optional().describe("New title"),
15118
15157
  status: external_exports.string().optional().describe("New status"),
15119
15158
  content: external_exports.string().optional().describe("New content"),
15120
- owner: external_exports.string().optional().describe("New 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")
15121
15161
  },
15122
15162
  async (args) => {
15123
- 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;
15124
15166
  const doc = store.update(id, updates, content);
15125
15167
  return {
15126
15168
  content: [
@@ -15653,14 +15695,14 @@ function collectSprintSummaryData(store, sprintId) {
15653
15695
  const sprintItemIds = new Set(workItemDocs.map((d) => d.frontmatter.id));
15654
15696
  for (const doc of workItemDocs) {
15655
15697
  const about = doc.frontmatter.aboutArtifact;
15656
- const streamTag = (doc.frontmatter.tags ?? []).find((t) => t.startsWith("stream:"));
15698
+ const focusTag = (doc.frontmatter.tags ?? []).find((t) => t.startsWith("focus:"));
15657
15699
  const item = {
15658
15700
  id: doc.frontmatter.id,
15659
15701
  title: doc.frontmatter.title,
15660
15702
  type: doc.frontmatter.type,
15661
15703
  status: doc.frontmatter.status,
15662
15704
  progress: getEffectiveProgress(doc.frontmatter),
15663
- workStream: streamTag ? streamTag.slice(7) : void 0,
15705
+ workFocus: focusTag ? focusTag.slice(6) : void 0,
15664
15706
  aboutArtifact: about
15665
15707
  };
15666
15708
  allItemsById.set(item.id, item);
@@ -16815,7 +16857,8 @@ function createFeatureTools(store) {
16815
16857
  title: external_exports.string().describe("Feature title"),
16816
16858
  content: external_exports.string().describe("Feature description and requirements"),
16817
16859
  status: external_exports.enum(["draft", "approved", "deferred", "done"]).optional().describe("Feature status (default: 'draft')"),
16818
- owner: external_exports.string().optional().describe("Feature 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"),
16819
16862
  priority: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("Feature priority"),
16820
16863
  tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization")
16821
16864
  },
@@ -16824,7 +16867,8 @@ function createFeatureTools(store) {
16824
16867
  title: args.title,
16825
16868
  status: args.status ?? "draft"
16826
16869
  };
16827
- if (args.owner) frontmatter.owner = args.owner;
16870
+ if (args.owner) frontmatter.owner = normalizeOwner(args.owner);
16871
+ if (args.assignee) frontmatter.assignee = args.assignee;
16828
16872
  if (args.priority) frontmatter.priority = args.priority;
16829
16873
  if (args.tags) frontmatter.tags = args.tags;
16830
16874
  const doc = store.create("feature", frontmatter, args.content);
@@ -16846,12 +16890,15 @@ function createFeatureTools(store) {
16846
16890
  title: external_exports.string().optional().describe("New title"),
16847
16891
  status: external_exports.enum(["draft", "approved", "deferred", "done"]).optional().describe("New status"),
16848
16892
  content: external_exports.string().optional().describe("New content"),
16849
- owner: external_exports.string().optional().describe("New 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"),
16850
16895
  priority: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("New priority"),
16851
16896
  tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove 'risk', add 'risk-mitigated')")
16852
16897
  },
16853
16898
  async (args) => {
16854
- 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;
16855
16902
  const doc = store.update(id, updates, content);
16856
16903
  return {
16857
16904
  content: [
@@ -16949,7 +16996,8 @@ function createEpicTools(store) {
16949
16996
  content: external_exports.string().describe("Epic description and scope"),
16950
16997
  linkedFeature: linkedFeatureArray.describe("Feature ID(s) to link this epic to (e.g. ['F-001'] or ['F-001', 'F-002'])"),
16951
16998
  status: external_exports.enum(["planned", "in-progress", "done"]).optional().describe("Epic status (default: 'planned')"),
16952
- owner: external_exports.string().optional().describe("Epic 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"),
16953
17001
  targetDate: external_exports.string().optional().describe("Target completion date (ISO format)"),
16954
17002
  estimatedEffort: external_exports.string().optional().describe("Estimated effort (e.g. '2 weeks', '5 story points')"),
16955
17003
  tags: external_exports.array(external_exports.string()).optional().describe("Additional tags")
@@ -16998,7 +17046,8 @@ function createEpicTools(store) {
16998
17046
  linkedFeature: linkedFeatures,
16999
17047
  tags: [...generateFeatureTags(linkedFeatures), ...args.tags ?? []]
17000
17048
  };
17001
- if (args.owner) frontmatter.owner = args.owner;
17049
+ if (args.owner) frontmatter.owner = normalizeOwner(args.owner);
17050
+ if (args.assignee) frontmatter.assignee = args.assignee;
17002
17051
  if (args.targetDate) frontmatter.targetDate = args.targetDate;
17003
17052
  if (args.estimatedEffort) frontmatter.estimatedEffort = args.estimatedEffort;
17004
17053
  const doc = store.create("epic", frontmatter, args.content);
@@ -17020,14 +17069,17 @@ function createEpicTools(store) {
17020
17069
  title: external_exports.string().optional().describe("New title"),
17021
17070
  status: external_exports.enum(["planned", "in-progress", "done"]).optional().describe("New status"),
17022
17071
  content: external_exports.string().optional().describe("New content"),
17023
- owner: external_exports.string().optional().describe("New 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"),
17024
17074
  targetDate: external_exports.string().optional().describe("New target date"),
17025
17075
  estimatedEffort: external_exports.string().optional().describe("New estimated effort"),
17026
17076
  linkedFeature: linkedFeatureArray.optional().describe("New linked feature ID(s)"),
17027
17077
  tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove 'risk', add 'risk-mitigated')")
17028
17078
  },
17029
17079
  async (args) => {
17030
- 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;
17031
17083
  if (rawLinkedFeature !== void 0) {
17032
17084
  const linkedFeatures = normalizeLinkedFeatures(rawLinkedFeature);
17033
17085
  for (const featureId of linkedFeatures) {
@@ -17154,7 +17206,7 @@ function createContributionTools(store) {
17154
17206
  aboutArtifact: external_exports.string().describe("Artifact ID this contribution relates to (e.g. 'A-001', 'T-003')"),
17155
17207
  status: external_exports.string().optional().describe("Status (default: 'done')"),
17156
17208
  tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization"),
17157
- workStream: external_exports.string().optional().describe("Work stream name (e.g. 'Budget UX'). Adds a stream:<value> tag."),
17209
+ workFocus: external_exports.string().optional().describe("Work focus name (e.g. 'Budget UX'). Adds a focus:<value> tag."),
17158
17210
  parentProgress: external_exports.number().optional().describe("Set progress (0-100) on the parent artifact (e.g. task or action). Propagates up the hierarchy.")
17159
17211
  },
17160
17212
  async (args) => {
@@ -17166,7 +17218,7 @@ function createContributionTools(store) {
17166
17218
  };
17167
17219
  frontmatter.aboutArtifact = args.aboutArtifact;
17168
17220
  const tags = [...args.tags ?? []];
17169
- if (args.workStream) tags.push(`stream:${args.workStream}`);
17221
+ if (args.workFocus) tags.push(`focus:${args.workFocus}`);
17170
17222
  if (tags.length > 0) frontmatter.tags = tags;
17171
17223
  const doc = store.create("contribution", frontmatter, args.content);
17172
17224
  const progressParts = [];
@@ -17222,15 +17274,15 @@ function createContributionTools(store) {
17222
17274
  title: external_exports.string().optional().describe("New title"),
17223
17275
  status: external_exports.string().optional().describe("New status"),
17224
17276
  content: external_exports.string().optional().describe("New content"),
17225
- workStream: external_exports.string().optional().describe("Work stream name (e.g. 'Budget UX'). Replaces existing stream:<value> tag.")
17277
+ workFocus: external_exports.string().optional().describe("Work focus name (e.g. 'Budget UX'). Replaces existing focus:<value> tag.")
17226
17278
  },
17227
17279
  async (args) => {
17228
- const { id, content, workStream, ...updates } = args;
17229
- if (workStream !== void 0) {
17280
+ const { id, content, workFocus, ...updates } = args;
17281
+ if (workFocus !== void 0) {
17230
17282
  const existing = store.get(id);
17231
17283
  const existingTags = existing?.frontmatter.tags ?? [];
17232
- const filtered = existingTags.filter((t) => !t.startsWith("stream:"));
17233
- filtered.push(`stream:${workStream}`);
17284
+ const filtered = existingTags.filter((t) => !t.startsWith("focus:"));
17285
+ filtered.push(`focus:${workFocus}`);
17234
17286
  updates.tags = filtered;
17235
17287
  }
17236
17288
  const oldDoc = store.get(id);
@@ -17713,7 +17765,7 @@ function createTaskTools(store) {
17713
17765
  complexity: external_exports.enum(["trivial", "simple", "moderate", "complex", "very-complex"]).optional().describe("Task complexity"),
17714
17766
  priority: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("Task priority"),
17715
17767
  tags: external_exports.array(external_exports.string()).optional().describe("Additional tags"),
17716
- workStream: external_exports.string().optional().describe("Work stream name (e.g. 'Budget UX'). Adds a stream:<value> tag.")
17768
+ workFocus: external_exports.string().optional().describe("Work focus name (e.g. 'Budget UX'). Adds a focus:<value> tag.")
17717
17769
  },
17718
17770
  async (args) => {
17719
17771
  const linkedEpics = normalizeLinkedEpics(args.linkedEpic);
@@ -17727,7 +17779,7 @@ function createTaskTools(store) {
17727
17779
  }
17728
17780
  }
17729
17781
  const baseTags = [...generateEpicTags(linkedEpics), ...args.tags ?? []];
17730
- if (args.workStream) baseTags.push(`stream:${args.workStream}`);
17782
+ if (args.workFocus) baseTags.push(`focus:${args.workFocus}`);
17731
17783
  const frontmatter = {
17732
17784
  title: args.title,
17733
17785
  status: args.status ?? "backlog",
@@ -17768,11 +17820,11 @@ function createTaskTools(store) {
17768
17820
  complexity: external_exports.enum(["trivial", "simple", "moderate", "complex", "very-complex"]).optional().describe("New complexity"),
17769
17821
  priority: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("New priority"),
17770
17822
  tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove old tags, add new ones)"),
17771
- workStream: external_exports.string().optional().describe("Work stream name (e.g. 'Budget UX'). Replaces existing stream:<value> tag."),
17823
+ workFocus: external_exports.string().optional().describe("Work focus name (e.g. 'Budget UX'). Replaces existing focus:<value> tag."),
17772
17824
  progress: external_exports.number().optional().describe("Explicit progress percentage (0-100). Overrides auto-calculation from child contributions.")
17773
17825
  },
17774
17826
  async (args) => {
17775
- const { id, content, linkedEpic: rawLinkedEpic, tags: userTags, workStream, progress, ...updates } = args;
17827
+ const { id, content, linkedEpic: rawLinkedEpic, tags: userTags, workFocus, progress, ...updates } = args;
17776
17828
  const warnings = [];
17777
17829
  if (rawLinkedEpic !== void 0) {
17778
17830
  const linkedEpics = normalizeLinkedEpics(rawLinkedEpic);
@@ -17793,10 +17845,10 @@ function createTaskTools(store) {
17793
17845
  } else if (userTags !== void 0) {
17794
17846
  updates.tags = userTags;
17795
17847
  }
17796
- if (workStream !== void 0) {
17848
+ if (workFocus !== void 0) {
17797
17849
  const currentTags = updates.tags ?? store.get(id)?.frontmatter.tags ?? [];
17798
- const filtered = currentTags.filter((t) => !t.startsWith("stream:"));
17799
- filtered.push(`stream:${workStream}`);
17850
+ const filtered = currentTags.filter((t) => !t.startsWith("focus:"));
17851
+ filtered.push(`focus:${workFocus}`);
17800
17852
  updates.tags = filtered;
17801
17853
  }
17802
17854
  if (typeof progress === "number") {
@@ -20023,7 +20075,7 @@ ${fragment}`);
20023
20075
  }
20024
20076
 
20025
20077
  // src/skills/action-tools.ts
20026
- import { tool as tool23 } from "@anthropic-ai/claude-agent-sdk";
20078
+ import { tool as tool24 } from "@anthropic-ai/claude-agent-sdk";
20027
20079
 
20028
20080
  // src/skills/action-runner.ts
20029
20081
  import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
@@ -20078,7 +20130,7 @@ function formatDate(iso) {
20078
20130
  if (!iso) return "";
20079
20131
  return iso.slice(0, 10);
20080
20132
  }
20081
- function typeLabel(type) {
20133
+ function typeLabel2(type) {
20082
20134
  return type.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
20083
20135
  }
20084
20136
  function renderMarkdown(md) {
@@ -21833,12 +21885,45 @@ tr:hover td {
21833
21885
  font-weight: 700;
21834
21886
  color: var(--text);
21835
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
+ }
21836
21921
  `;
21837
21922
  }
21838
21923
 
21839
21924
  // src/web/templates/pages/documents.ts
21840
21925
  function documentsPage(data) {
21841
- const label = typeLabel(data.type);
21926
+ const label = typeLabel2(data.type);
21842
21927
  const statusOptions = data.statuses.map(
21843
21928
  (s) => `<option value="${escapeHtml(s)}"${data.filterStatus === s ? " selected" : ""}>${escapeHtml(s)}</option>`
21844
21929
  ).join("");
@@ -21912,7 +21997,7 @@ function documentsPage(data) {
21912
21997
  // src/web/templates/pages/document-detail.ts
21913
21998
  function documentDetailPage(doc) {
21914
21999
  const fm = doc.frontmatter;
21915
- const label = typeLabel(fm.type);
22000
+ const label = typeLabel2(fm.type);
21916
22001
  const skipKeys = /* @__PURE__ */ new Set(["title", "type"]);
21917
22002
  const entries = Object.entries(fm).filter(
21918
22003
  ([key]) => !skipKeys.has(key) && fm[key] != null
@@ -22611,7 +22696,7 @@ function poDashboardPage(ctx) {
22611
22696
  <tr>
22612
22697
  <td><a href="/docs/${d.frontmatter.type}/${escapeHtml(d.frontmatter.id)}">${escapeHtml(d.frontmatter.id)}</a></td>
22613
22698
  <td>${escapeHtml(d.frontmatter.title)}</td>
22614
- <td>${escapeHtml(typeLabel(d.frontmatter.type))}</td>
22699
+ <td>${escapeHtml(typeLabel2(d.frontmatter.type))}</td>
22615
22700
  <td>${statusBadge(d.frontmatter.status)}</td>
22616
22701
  <td>${formatDate(d.frontmatter.updated ?? d.frontmatter.created)}</td>
22617
22702
  </tr>`).join("")}
@@ -23119,7 +23204,7 @@ function poDeliveryPage(ctx) {
23119
23204
  <tr>
23120
23205
  <td><a href="/docs/${escapeHtml(d.frontmatter.type)}/${escapeHtml(d.frontmatter.id)}">${escapeHtml(d.frontmatter.id)}</a></td>
23121
23206
  <td>${escapeHtml(d.frontmatter.title)}</td>
23122
- <td>${escapeHtml(typeLabel(d.frontmatter.type))}</td>
23207
+ <td>${escapeHtml(typeLabel2(d.frontmatter.type))}</td>
23123
23208
  <td>${statusBadge(d.frontmatter.status)}</td>
23124
23209
  <td>${formatDate(d.frontmatter.updated ?? d.frontmatter.created)}</td>
23125
23210
  </tr>`).join("")}
@@ -23437,15 +23522,15 @@ function sprintSummaryPage(data, cached2) {
23437
23522
  </div>`,
23438
23523
  { titleTag: "h3" }
23439
23524
  ) : "";
23440
- const STREAM_PALETTE = [
23441
- "hsla(220, 30%, 22%, 0.45)",
23442
- "hsla(160, 30%, 20%, 0.45)",
23443
- "hsla(280, 25%, 22%, 0.45)",
23444
- "hsla(30, 35%, 22%, 0.45)",
23445
- "hsla(340, 25%, 22%, 0.45)",
23446
- "hsla(190, 30%, 20%, 0.45)",
23447
- "hsla(60, 25%, 20%, 0.45)",
23448
- "hsla(120, 20%, 20%, 0.45)"
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%)"
23449
23534
  ];
23450
23535
  function hashString(s) {
23451
23536
  let h = 0;
@@ -23454,68 +23539,92 @@ function sprintSummaryPage(data, cached2) {
23454
23539
  }
23455
23540
  return Math.abs(h);
23456
23541
  }
23457
- function collectStreams(items) {
23458
- const streams = /* @__PURE__ */ new Set();
23459
- for (const w of items) {
23460
- if (w.workStream) streams.add(w.workStream);
23461
- if (w.children) {
23462
- for (const s of collectStreams(w.children)) streams.add(s);
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);
23463
23565
  }
23464
23566
  }
23465
- return streams;
23567
+ walk(items);
23568
+ return { total, done, inProgress };
23466
23569
  }
23467
- const uniqueStreams = collectStreams(data.workItems.items);
23468
- const streamColorMap = /* @__PURE__ */ new Map();
23469
- for (const name of uniqueStreams) {
23470
- streamColorMap.set(name, STREAM_PALETTE[hashString(name) % STREAM_PALETTE.length]);
23471
- }
23472
- const streamStyleRules = [...streamColorMap.entries()].map(([name, color]) => `tr[data-stream="${escapeHtml(name)}"] td { background: ${color}; }`).join("\n");
23473
- const streamStyleBlock = streamStyleRules ? `<style>${streamStyleRules}</style>` : "";
23474
- function renderItemRows(items, depth = 0) {
23570
+ function renderItemRows(items, borderColor, depth = 0) {
23475
23571
  return items.flatMap((w) => {
23476
23572
  const isChild = depth > 0;
23477
23573
  const isContribution = w.type === "contribution";
23478
- const classes = [];
23574
+ const classes = ["focus-row"];
23479
23575
  if (isContribution) classes.push("contribution-row");
23480
23576
  else if (isChild) classes.push("child-row");
23481
- const dataStream = w.workStream ? ` data-stream="${escapeHtml(w.workStream)}"` : "";
23482
- const rowAttrs = classes.length > 0 ? ` class="${classes.join(" ")}"` : "";
23483
23577
  const indent = depth > 0 ? ` style="padding-left: ${0.75 + depth * 1}rem"` : "";
23484
- const streamCell = w.workStream ? `<span class="badge badge-subtle">${escapeHtml(w.workStream)}</span>` : "";
23485
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>` : "";
23486
23579
  const row = `
23487
- <tr${rowAttrs}${dataStream}>
23580
+ <tr class="${classes.join(" ")}" style="--focus-color: ${borderColor}">
23488
23581
  <td${indent}><a href="/docs/${escapeHtml(w.type)}/${escapeHtml(w.id)}">${escapeHtml(w.id)}</a></td>
23489
23582
  <td>${escapeHtml(w.title)}</td>
23490
- <td>${streamCell}</td>
23491
- <td>${escapeHtml(typeLabel(w.type))}</td>
23492
23583
  <td>${statusBadge(w.status)}</td>
23493
23584
  <td>${progressCell}</td>
23494
23585
  </tr>`;
23495
- const childRows = w.children ? renderItemRows(w.children, depth + 1) : [];
23586
+ const childRows = w.children ? renderItemRows(w.children, borderColor, depth + 1) : [];
23496
23587
  return [row, ...childRows];
23497
23588
  });
23498
23589
  }
23499
- const workItemRows = renderItemRows(data.workItems.items);
23500
- const sortableHeaders = `<tr>
23501
- <th class="sortable-th" onclick="sortWorkItems(0)">ID<span class="sort-arrow" id="sort-arrow-0"></span></th>
23502
- <th class="sortable-th" onclick="sortWorkItems(1)">Title<span class="sort-arrow" id="sort-arrow-1"></span></th>
23503
- <th class="sortable-th" onclick="sortWorkItems(2)">Stream<span class="sort-arrow" id="sort-arrow-2"></span></th>
23504
- <th class="sortable-th" onclick="sortWorkItems(3)">Type<span class="sort-arrow" id="sort-arrow-3"></span></th>
23505
- <th class="sortable-th" onclick="sortWorkItems(4)">Status<span class="sort-arrow" id="sort-arrow-4"></span></th>
23506
- <th class="sortable-th" onclick="sortWorkItems(5)">Progress<span class="sort-arrow" id="sort-arrow-5"></span></th>
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>
23507
23617
  </tr>`;
23508
- const workItemsSection = workItemRows.length > 0 ? collapsibleSection(
23618
+ const workItemsSection = allWorkItemRows.length > 0 ? collapsibleSection(
23509
23619
  "ss-work-items",
23510
23620
  "Work Items",
23511
- `${streamStyleBlock}
23512
- <div class="table-wrap">
23621
+ `<div class="table-wrap">
23513
23622
  <table id="work-items-table">
23514
23623
  <thead>
23515
- ${sortableHeaders}
23624
+ ${tableHeaders}
23516
23625
  </thead>
23517
23626
  <tbody>
23518
- ${workItemRows.join("")}
23627
+ ${allWorkItemRows.join("")}
23519
23628
  </tbody>
23520
23629
  </table>
23521
23630
  </div>`,
@@ -23591,61 +23700,6 @@ function sprintSummaryPage(data, cached2) {
23591
23700
  </div>
23592
23701
 
23593
23702
  <script>
23594
- var _sortCol = -1;
23595
- var _sortAsc = true;
23596
-
23597
- function sortWorkItems(col) {
23598
- var table = document.getElementById('work-items-table');
23599
- if (!table) return;
23600
- var tbody = table.querySelector('tbody');
23601
- var allRows = Array.from(tbody.querySelectorAll('tr'));
23602
-
23603
- // Toggle direction if clicking the same column
23604
- if (_sortCol === col) {
23605
- _sortAsc = !_sortAsc;
23606
- } else {
23607
- _sortCol = col;
23608
- _sortAsc = true;
23609
- }
23610
-
23611
- // Update sort arrows
23612
- for (var i = 0; i < 6; i++) {
23613
- var arrow = document.getElementById('sort-arrow-' + i);
23614
- if (arrow) arrow.textContent = i === col ? (_sortAsc ? ' \\u25B2' : ' \\u25BC') : '';
23615
- }
23616
-
23617
- // Group rows: root rows + their child/contribution rows
23618
- var groups = [];
23619
- var current = null;
23620
- for (var r = 0; r < allRows.length; r++) {
23621
- var row = allRows[r];
23622
- var isChild = row.classList.contains('child-row') || row.classList.contains('contribution-row');
23623
- if (!isChild) {
23624
- current = { root: row, children: [] };
23625
- groups.push(current);
23626
- } else if (current) {
23627
- current.children.push(row);
23628
- }
23629
- }
23630
-
23631
- // Sort groups by root row text content of target column
23632
- groups.sort(function(a, b) {
23633
- var aText = (a.root.children[col] ? a.root.children[col].textContent : '').trim().toLowerCase();
23634
- var bText = (b.root.children[col] ? b.root.children[col].textContent : '').trim().toLowerCase();
23635
- if (aText < bText) return _sortAsc ? -1 : 1;
23636
- if (aText > bText) return _sortAsc ? 1 : -1;
23637
- return 0;
23638
- });
23639
-
23640
- // Re-append rows in sorted order
23641
- for (var g = 0; g < groups.length; g++) {
23642
- tbody.appendChild(groups[g].root);
23643
- for (var c = 0; c < groups[g].children.length; c++) {
23644
- tbody.appendChild(groups[g].children[c]);
23645
- }
23646
- }
23647
- }
23648
-
23649
23703
  async function generateSummary() {
23650
23704
  var btn = document.getElementById('generate-btn');
23651
23705
  var loading = document.getElementById('summary-loading');
@@ -23847,7 +23901,7 @@ function dmRisksPage(ctx) {
23847
23901
  <tr>
23848
23902
  <td><a href="/docs/${escapeHtml(d.frontmatter.type)}/${escapeHtml(d.frontmatter.id)}">${escapeHtml(d.frontmatter.id)}</a></td>
23849
23903
  <td>${escapeHtml(d.frontmatter.title)}</td>
23850
- <td>${escapeHtml(typeLabel(d.frontmatter.type))}</td>
23904
+ <td>${escapeHtml(typeLabel2(d.frontmatter.type))}</td>
23851
23905
  <td>${d.frontmatter.owner ? escapeHtml(d.frontmatter.owner) : '<span class="text-dim">\u2014</span>'}</td>
23852
23906
  <td>${formatDate(d.frontmatter.created)}</td>
23853
23907
  </tr>`).join("")}
@@ -23871,7 +23925,7 @@ function dmRisksPage(ctx) {
23871
23925
  <tr>
23872
23926
  <td><a href="/docs/${escapeHtml(d.frontmatter.type)}/${escapeHtml(d.frontmatter.id)}">${escapeHtml(d.frontmatter.id)}</a></td>
23873
23927
  <td>${escapeHtml(d.frontmatter.title)}</td>
23874
- <td>${escapeHtml(typeLabel(d.frontmatter.type))}</td>
23928
+ <td>${escapeHtml(typeLabel2(d.frontmatter.type))}</td>
23875
23929
  <td>${statusBadge(d.frontmatter.status)}</td>
23876
23930
  <td>${formatDate(d.frontmatter.created)}</td>
23877
23931
  <td><span class="${ageDays > 30 ? "priority-high" : "priority-medium"}">${ageDays}d</span></td>
@@ -24436,16 +24490,16 @@ function tlSprintPage(ctx) {
24436
24490
  `<div class="table-wrap">
24437
24491
  <table>
24438
24492
  <thead>
24439
- <tr><th>ID</th><th>Title</th><th>Type</th><th>Status</th><th>Stream</th></tr>
24493
+ <tr><th>ID</th><th>Title</th><th>Type</th><th>Status</th><th>Focus</th></tr>
24440
24494
  </thead>
24441
24495
  <tbody>
24442
24496
  ${techItems.map((w) => `
24443
24497
  <tr>
24444
24498
  <td><a href="/docs/${escapeHtml(w.type)}/${escapeHtml(w.id)}">${escapeHtml(w.id)}</a></td>
24445
24499
  <td>${escapeHtml(w.title)}</td>
24446
- <td>${escapeHtml(typeLabel(w.type))}</td>
24500
+ <td>${escapeHtml(typeLabel2(w.type))}</td>
24447
24501
  <td>${statusBadge(w.status)}</td>
24448
- <td>${w.workStream ? `<span class="badge badge-subtle">${escapeHtml(w.workStream)}</span>` : '<span class="text-dim">\u2014</span>'}</td>
24502
+ <td>${w.workFocus ? `<span class="badge badge-subtle">${escapeHtml(w.workFocus)}</span>` : '<span class="text-dim">\u2014</span>'}</td>
24449
24503
  </tr>`).join("")}
24450
24504
  </tbody>
24451
24505
  </table>
@@ -24465,7 +24519,7 @@ function tlSprintPage(ctx) {
24465
24519
  <tr>
24466
24520
  <td><a href="/docs/${escapeHtml(d.frontmatter.type)}/${escapeHtml(d.frontmatter.id)}">${escapeHtml(d.frontmatter.id)}</a></td>
24467
24521
  <td>${escapeHtml(d.frontmatter.title)}</td>
24468
- <td>${escapeHtml(typeLabel(d.frontmatter.type))}</td>
24522
+ <td>${escapeHtml(typeLabel2(d.frontmatter.type))}</td>
24469
24523
  <td>${statusBadge(d.frontmatter.status)}</td>
24470
24524
  <td>${formatDate(d.frontmatter.updated ?? d.frontmatter.created)}</td>
24471
24525
  </tr>`).join("")}
@@ -24654,7 +24708,7 @@ function timelinePage(diagrams) {
24654
24708
  // src/web/templates/pages/board.ts
24655
24709
  function boardPage(data, basePath = "/board") {
24656
24710
  const typeOptions = data.types.map(
24657
- (t) => `<option value="${escapeHtml(t)}"${data.type === t ? " selected" : ""}>${escapeHtml(typeLabel(t))}s</option>`
24711
+ (t) => `<option value="${escapeHtml(t)}"${data.type === t ? " selected" : ""}>${escapeHtml(typeLabel2(t))}s</option>`
24658
24712
  ).join("");
24659
24713
  const columns = data.columns.map(
24660
24714
  (col) => `
@@ -24811,7 +24865,7 @@ function upcomingPage(data) {
24811
24865
  <td><span class="trending-rank">${i + 1}</span></td>
24812
24866
  <td><a href="/docs/${escapeHtml(t.type)}/${escapeHtml(t.id)}">${escapeHtml(t.id)}</a></td>
24813
24867
  <td>${escapeHtml(t.title)}</td>
24814
- <td>${escapeHtml(typeLabel(t.type))}</td>
24868
+ <td>${escapeHtml(typeLabel2(t.type))}</td>
24815
24869
  <td>${statusBadge(t.status)}</td>
24816
24870
  <td><span class="trending-score">${t.score}</span></td>
24817
24871
  <td>${t.signals.map((s) => `<span class="signal-tag">${escapeHtml(s.factor)} +${s.points}</span>`).join(" ")}</td>
@@ -24905,7 +24959,7 @@ function buildPersonaLayoutOpts(persona, activePath, navGroups) {
24905
24959
  const artifactGroupsHtml = navGroups.map((group) => {
24906
24960
  const links = group.types.map((type) => {
24907
24961
  const href = `/docs/${type}?persona=${persona}`;
24908
- return `<a href="${href}" class="${isActive(`/docs/${type}`)}">${typeLabel(type)}s</a>`;
24962
+ return `<a href="${href}" class="${isActive(`/docs/${type}`)}">${typeLabel2(type)}s</a>`;
24909
24963
  }).join("\n ");
24910
24964
  const groupActive = group.types.some(
24911
24965
  (type) => isActive(`/docs/${type}`) !== ""
@@ -25331,6 +25385,556 @@ function createWebTools(store, projectName, navGroups) {
25331
25385
  ];
25332
25386
  }
25333
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
+
25334
25938
  // src/agent/mcp-server.ts
25335
25939
  function createMarvinMcpServer(store, options) {
25336
25940
  const tools = [
@@ -25342,7 +25946,8 @@ function createMarvinMcpServer(store, options) {
25342
25946
  ...options?.sessionStore ? createSessionTools(options.sessionStore) : [],
25343
25947
  ...options?.pluginTools ?? [],
25344
25948
  ...options?.skillTools ?? [],
25345
- ...options?.projectName && options?.navGroups ? createWebTools(store, options.projectName, options.navGroups) : []
25949
+ ...options?.projectName && options?.navGroups ? createWebTools(store, options.projectName, options.navGroups) : [],
25950
+ ...createDoctorTools(store)
25346
25951
  ];
25347
25952
  return createSdkMcpServer({
25348
25953
  name: "marvin-governance",
@@ -25414,7 +26019,7 @@ function createSkillActionTools(skills, context) {
25414
26019
  if (!skill.actions) continue;
25415
26020
  for (const action of skill.actions) {
25416
26021
  tools.push(
25417
- tool23(
26022
+ tool24(
25418
26023
  `${skill.id}__${action.id}`,
25419
26024
  action.description,
25420
26025
  {
@@ -25506,10 +26111,10 @@ ${lines.join("\n\n")}`;
25506
26111
  }
25507
26112
 
25508
26113
  // src/mcp/persona-tools.ts
25509
- import { tool as tool24 } from "@anthropic-ai/claude-agent-sdk";
26114
+ import { tool as tool25 } from "@anthropic-ai/claude-agent-sdk";
25510
26115
  function createPersonaTools(ctx, marvinDir) {
25511
26116
  return [
25512
- tool24(
26117
+ tool25(
25513
26118
  "set_persona",
25514
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.",
25515
26120
  {
@@ -25539,7 +26144,7 @@ ${summaries}`
25539
26144
  };
25540
26145
  }
25541
26146
  ),
25542
- tool24(
26147
+ tool25(
25543
26148
  "get_persona_guidance",
25544
26149
  "Get guidance for a persona without changing the active persona. If no persona is specified, lists all available personas with summaries.",
25545
26150
  {