mrvn-cli 0.5.0 → 0.5.2

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