@kaddo/mcp 3.130.1 → 3.131.0

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.
Files changed (2) hide show
  1. package/dist/index.js +1257 -583
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -391,7 +391,7 @@ function join(...parts) {
391
391
  }
392
392
 
393
393
  // ../cli/src/core/impact-report.ts
394
- import matter12 from "gray-matter";
394
+ import matter13 from "gray-matter";
395
395
 
396
396
  // ../cli/src/core/config.ts
397
397
  import { z } from "zod";
@@ -2599,6 +2599,9 @@ function parseInitiative(dir, filePath, raw) {
2599
2599
  source: optStr(data.source) ?? null,
2600
2600
  sourceId: optStr(data.source_id) ?? null,
2601
2601
  createdAt: optStr(data.created_at) ?? null,
2602
+ epic: optStr(data.epic) ?? null,
2603
+ owners: strList(data.owners),
2604
+ contributors: strList(data.contributors),
2602
2605
  externalLinks: parseExternalLinks(data),
2603
2606
  candidates: parseCandidates(data),
2604
2607
  filePath,
@@ -2621,8 +2624,123 @@ function discoverInitiatives(dir) {
2621
2624
  return out.sort((a2, b2) => a2.id.localeCompare(b2.id));
2622
2625
  }
2623
2626
 
2624
- // ../cli/src/core/resources.ts
2627
+ // ../cli/src/core/delivery-planning.ts
2625
2628
  import matter6 from "gray-matter";
2629
+ var DELIVERY_PLANS_DIR = "knowledge/delivery/plans";
2630
+ var EPICS_DIR = "knowledge/delivery/epics";
2631
+ var PLAN_STATES = ["draft", "in-progress", "completed", "cancelled"];
2632
+ var EPIC_STATES = ["planned", "in-progress", "completed", "cancelled"];
2633
+ function toPosix4(p) {
2634
+ return p.replace(/\\/g, "/");
2635
+ }
2636
+ function optStr2(v) {
2637
+ if (typeof v === "string" && v.trim()) return v.trim();
2638
+ if (typeof v === "number") return String(v);
2639
+ return void 0;
2640
+ }
2641
+ function strList2(v) {
2642
+ if (!Array.isArray(v)) return [];
2643
+ return v.map((x2) => typeof x2 === "string" ? x2.trim() : String(x2)).filter(Boolean);
2644
+ }
2645
+ function relFrom(dir, filePath) {
2646
+ const d3 = toPosix4(dir);
2647
+ const f3 = toPosix4(filePath);
2648
+ return f3.startsWith(d3 + "/") ? f3.slice(d3.length + 1) : f3;
2649
+ }
2650
+ function parseMilestones(data) {
2651
+ const raw = data.milestones;
2652
+ if (!Array.isArray(raw)) return [];
2653
+ const out = [];
2654
+ for (const entry of raw) {
2655
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
2656
+ const o2 = entry;
2657
+ const id = optStr2(o2.id);
2658
+ if (!id) continue;
2659
+ out.push({ id, title: optStr2(o2.title) ?? id, exitCriteria: strList2(o2.exit_criteria) });
2660
+ }
2661
+ return out;
2662
+ }
2663
+ function parseDeliveryPlan(dir, filePath, raw) {
2664
+ let parsed;
2665
+ try {
2666
+ parsed = matter6(raw, {});
2667
+ } catch {
2668
+ return null;
2669
+ }
2670
+ const data = parsed.data;
2671
+ if (optStr2(data.type) !== "delivery-plan") return null;
2672
+ const id = optStr2(data.id);
2673
+ if (!id) return null;
2674
+ const statusRaw = optStr2(data.status) ?? "draft";
2675
+ const status = PLAN_STATES.includes(statusRaw) ? statusRaw : "draft";
2676
+ return {
2677
+ id,
2678
+ title: optStr2(data.title) ?? id,
2679
+ status,
2680
+ objective: optStr2(data.objective) ?? null,
2681
+ milestones: parseMilestones(data),
2682
+ dependsOn: strList2(data.depends_on),
2683
+ successCriteria: strList2(data.success_criteria),
2684
+ risks: strList2(data.risks),
2685
+ references: strList2(data.references),
2686
+ filePath,
2687
+ relPath: relFrom(dir, filePath),
2688
+ body: parsed.content.trim(),
2689
+ rawFrontmatter: data
2690
+ };
2691
+ }
2692
+ function parseEpic(dir, filePath, raw) {
2693
+ let parsed;
2694
+ try {
2695
+ parsed = matter6(raw, {});
2696
+ } catch {
2697
+ return null;
2698
+ }
2699
+ const data = parsed.data;
2700
+ if (optStr2(data.type) !== "epic") return null;
2701
+ const id = optStr2(data.id);
2702
+ if (!id) return null;
2703
+ const statusRaw = optStr2(data.status) ?? "planned";
2704
+ const status = EPIC_STATES.includes(statusRaw) ? statusRaw : "planned";
2705
+ return {
2706
+ id,
2707
+ title: optStr2(data.title) ?? id,
2708
+ status,
2709
+ outcome: optStr2(data.outcome ?? data.expected_outcome) ?? null,
2710
+ plan: optStr2(data.plan) ?? null,
2711
+ milestone: optStr2(data.milestone) ?? null,
2712
+ dependsOn: strList2(data.depends_on),
2713
+ successCriteria: strList2(data.success_criteria),
2714
+ relatedCapabilities: strList2(data.related_capabilities),
2715
+ decisions: strList2(data.decisions),
2716
+ filePath,
2717
+ relPath: relFrom(dir, filePath),
2718
+ body: parsed.content.trim(),
2719
+ rawFrontmatter: data
2720
+ };
2721
+ }
2722
+ function discoverIn(dir, rel, parse3) {
2723
+ const d3 = join(dir, rel);
2724
+ if (!exists(d3)) return [];
2725
+ const out = [];
2726
+ for (const entry of readDir(d3)) {
2727
+ if (!entry.endsWith(".md")) continue;
2728
+ const full = join(d3, entry);
2729
+ if (!isFile(full)) continue;
2730
+ const parsed = parse3(dir, full, readFile(full));
2731
+ if (parsed) out.push(parsed);
2732
+ }
2733
+ return out;
2734
+ }
2735
+ function discoverDeliveryPlans(dir) {
2736
+ return discoverIn(dir, DELIVERY_PLANS_DIR, parseDeliveryPlan).sort((a2, b2) => a2.id.localeCompare(b2.id));
2737
+ }
2738
+ function discoverEpics(dir) {
2739
+ return discoverIn(dir, EPICS_DIR, parseEpic).sort((a2, b2) => a2.id.localeCompare(b2.id));
2740
+ }
2741
+
2742
+ // ../cli/src/core/resources.ts
2743
+ import matter7 from "gray-matter";
2626
2744
  var RESOURCE_ARTIFACT_TYPE = "project-resource";
2627
2745
  var RESOURCE_TYPES = [
2628
2746
  "database",
@@ -2738,7 +2856,7 @@ function purposeFromBody(body) {
2738
2856
  return text3 || null;
2739
2857
  }
2740
2858
  function toDetail(filePath, relPath, raw) {
2741
- const parsed = matter6(raw);
2859
+ const parsed = matter7(raw);
2742
2860
  const fm = parsed.data ?? {};
2743
2861
  const body = parsed.content.trim();
2744
2862
  const id = str(fm.id) ?? "";
@@ -2800,7 +2918,7 @@ function parseWorkItemResources(fm) {
2800
2918
  }
2801
2919
 
2802
2920
  // ../cli/src/core/deprecation.ts
2803
- import matter7 from "gray-matter";
2921
+ import matter8 from "gray-matter";
2804
2922
  var DEPRECATION_ARTIFACT_TYPE = "deprecation-plan";
2805
2923
  var DEPRECATION_PLANS_DIR = "knowledge/legacy/deprecations";
2806
2924
  var TARGET_TYPES = [
@@ -2991,7 +3109,7 @@ function getDeprecationPlan(dir, id) {
2991
3109
  );
2992
3110
  if (!match) return null;
2993
3111
  const raw = readFile(match.filePath);
2994
- const parsed = matter7(raw);
3112
+ const parsed = matter8(raw);
2995
3113
  const fm = parsed.data ?? {};
2996
3114
  const body = parsed.content.trim();
2997
3115
  const targets = parseTargets(fm.targets);
@@ -3058,14 +3176,14 @@ var ALL_STATUSES = ["draft", "ready", "in-progress", "blocked", "completed"];
3058
3176
  function scopeStatuses(scope) {
3059
3177
  return scope === "all" ? { included: ALL_STATUSES, excluded: ["archived"] } : { included: ACTIVE_STATUSES, excluded: ["completed", "archived"] };
3060
3178
  }
3061
- function toPosix4(p) {
3179
+ function toPosix5(p) {
3062
3180
  return p.replace(/\\/g, "/");
3063
3181
  }
3064
3182
  function slug(s) {
3065
3183
  return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
3066
3184
  }
3067
3185
  function isAdr(a2) {
3068
- return toPosix4(a2.filePath).includes("/tech/decisions/") && Boolean(a2.type);
3186
+ return toPosix5(a2.filePath).includes("/tech/decisions/") && Boolean(a2.type);
3069
3187
  }
3070
3188
  function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
3071
3189
  const scope = opts.scope ?? "active";
@@ -3176,6 +3294,27 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
3176
3294
  }
3177
3295
  }
3178
3296
  }
3297
+ for (const plan of discoverDeliveryPlans(dir)) {
3298
+ const planId = `delivery-plan:${slug(plan.id) || plan.id}`;
3299
+ addNode({ id: planId, type: "delivery-plan", label: `${plan.id} ${plan.title}`.trim(), path: plan.relPath, status: plan.status });
3300
+ for (const m2 of plan.milestones) {
3301
+ const milestoneId = `milestone:${slug(plan.id)}:${slug(m2.id) || m2.id}`;
3302
+ addNode({ id: milestoneId, type: "milestone", label: `${m2.id} ${m2.title}`.trim(), path: plan.relPath });
3303
+ addEdge(milestoneId, planId, "belongs_to");
3304
+ }
3305
+ }
3306
+ for (const epic of discoverEpics(dir)) {
3307
+ const epicNodeId = `epic:${slug(epic.id) || epic.id}`;
3308
+ addNode({ id: epicNodeId, type: "epic", label: `${epic.id} ${epic.title}`.trim(), path: epic.relPath, status: epic.status });
3309
+ if (epic.plan) {
3310
+ addEdge(epicNodeId, `delivery-plan:${slug(epic.plan)}`, "belongs_to");
3311
+ if (epic.milestone) addEdge(epicNodeId, `milestone:${slug(epic.plan)}:${slug(epic.milestone)}`, "belongs_to");
3312
+ }
3313
+ for (const dep of epic.dependsOn) addEdge(epicNodeId, `epic:${slug(dep)}`, "depends_on");
3314
+ }
3315
+ for (const ini of discoverInitiatives(dir)) {
3316
+ if (ini.epic) addEdge(`initiative:${slug(ini.id) || ini.id}`, `epic:${slug(ini.epic)}`, "belongs_to");
3317
+ }
3179
3318
  for (const wi of selectedWIs) {
3180
3319
  const id = wi.id || wi.title;
3181
3320
  if (!id || !id.trim()) continue;
@@ -3347,14 +3486,14 @@ function loadGraphSummary(dir) {
3347
3486
 
3348
3487
  // ../cli/src/core/graph-hints.ts
3349
3488
  var KNOWLEDGE4 = "knowledge";
3350
- function toPosix5(p) {
3489
+ function toPosix6(p) {
3351
3490
  return p.replace(/\\/g, "/");
3352
3491
  }
3353
3492
  function slug2(s) {
3354
3493
  return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
3355
3494
  }
3356
3495
  function isAdr2(a2) {
3357
- return toPosix5(a2.filePath).includes("/tech/decisions/") && Boolean(a2.type);
3496
+ return toPosix6(a2.filePath).includes("/tech/decisions/") && Boolean(a2.type);
3358
3497
  }
3359
3498
  function capabilityHeadings(dir) {
3360
3499
  const p = join(dir, KNOWLEDGE4, "product", "capabilities.md");
@@ -3589,7 +3728,7 @@ function loadGraphHints(dir) {
3589
3728
  }
3590
3729
 
3591
3730
  // ../cli/src/services/installed-skills.ts
3592
- import matter8 from "gray-matter";
3731
+ import matter9 from "gray-matter";
3593
3732
  var SKILLS_DIR = join("knowledge", "skills");
3594
3733
  function discoverInstalledSkills(dir) {
3595
3734
  const base = join(dir, SKILLS_DIR);
@@ -3599,7 +3738,7 @@ function discoverInstalledSkills(dir) {
3599
3738
  const skillFile = join(base, entry, "skill.md");
3600
3739
  if (!isFile(skillFile)) continue;
3601
3740
  try {
3602
- const { data } = matter8(readFile(skillFile));
3741
+ const { data } = matter9(readFile(skillFile));
3603
3742
  if (data.type && String(data.type) !== "skill") continue;
3604
3743
  const id = String(data.id ?? entry);
3605
3744
  out.push({
@@ -4705,7 +4844,7 @@ function readPocSummary(dir) {
4705
4844
 
4706
4845
  // ../cli/src/core/poc-report.ts
4707
4846
  import { createHash } from "crypto";
4708
- import matter9 from "gray-matter";
4847
+ import matter10 from "gray-matter";
4709
4848
  var POC_REPORT_PREFIX = "poc-report-v";
4710
4849
  var SECRET_LINE = /(^|\s)(?:[A-Z][A-Z0-9_]*(?:TOKEN|SECRET|PASSWORD|API_KEY|PRIVATE_KEY)|(?:token|secret|password|api[_-]?key))\s*[:=]\s*\S+/i;
4711
4850
  var reportName = /^poc-report-v(\d{3,})\.md$/;
@@ -4733,7 +4872,7 @@ function reportFiles(dir) {
4733
4872
  const match = name.match(reportName);
4734
4873
  if (!match) return [];
4735
4874
  try {
4736
- const parsed = matter9(readFile(join(delivery, name)));
4875
+ const parsed = matter10(readFile(join(delivery, name)));
4737
4876
  const fingerprint = typeof parsed.data.source_fingerprint === "string" ? parsed.data.source_fingerprint : "";
4738
4877
  const manifest = parsed.data.source_manifest;
4739
4878
  const sourceManifest = manifest && typeof manifest === "object" && !Array.isArray(manifest) ? Object.fromEntries(Object.entries(manifest).filter(([, digest]) => typeof digest === "string")) : {};
@@ -5379,7 +5518,7 @@ function deriveDeliveryCompletedStatus(dir) {
5379
5518
  }
5380
5519
 
5381
5520
  // ../cli/src/core/assets.ts
5382
- import matter10 from "gray-matter";
5521
+ import matter11 from "gray-matter";
5383
5522
 
5384
5523
  // ../cli/src/core/version.ts
5385
5524
  import { fileURLToPath } from "url";
@@ -6343,9 +6482,9 @@ var RESPONSIBILITY_MATRIX = {
6343
6482
  },
6344
6483
  "roadmap-agent": {
6345
6484
  agent: "roadmap-agent",
6346
- responsibleFor: ["Roadmap", "Initiatives", "Work Item candidates"],
6485
+ responsibleFor: ["Roadmap", "Initiatives", "Work Item candidates", "Delivery Plans and Epics (optional planning layer)"],
6347
6486
  produces: ["knowledge/delivery/roadmap.md"],
6348
- canSuggest: ["kaddo create --from roadmap", "work-item-agent"],
6487
+ canSuggest: ["kaddo create --from roadmap", "work-item-agent", "kaddo plan create", "kaddo epic create"],
6349
6488
  cannotSuggest: ["branches", "commits", "pull requests", "code"],
6350
6489
  next: ["kaddo create --from roadmap", "work-item-agent"]
6351
6490
  },
@@ -8846,7 +8985,7 @@ function canonicalSkills() {
8846
8985
  }
8847
8986
  function versionOf(content) {
8848
8987
  try {
8849
- const v = matter10(content).data.version;
8988
+ const v = matter11(content).data.version;
8850
8989
  return v === void 0 || v === null ? null : String(v);
8851
8990
  } catch {
8852
8991
  return null;
@@ -9038,19 +9177,19 @@ function parseWorkItemSource(frontmatter) {
9038
9177
  const rawSource = frontmatter.source;
9039
9178
  if (rawSource != null && typeof rawSource === "object" && !Array.isArray(rawSource)) {
9040
9179
  const obj = rawSource;
9041
- const t = optStr2(obj.type);
9180
+ const t = optStr3(obj.type);
9042
9181
  const type = t && isValidSource(t) ? t : "unknown";
9043
9182
  return {
9044
9183
  type,
9045
- id: optStr2(obj.id) ?? optStr2(frontmatter.source_id),
9046
- title: optStr2(obj.title) ?? optStr2(frontmatter.source_title),
9047
- context: optStr2(obj.context) ?? optStr2(frontmatter.source_context),
9048
- provider: optStr2(obj.provider) ?? optStr2(frontmatter.source_provider),
9049
- integration: optStr2(obj.integration) ?? optStr2(frontmatter.source_integration),
9050
- url: optStr2(obj.url) ?? optStr2(frontmatter.source_url),
9051
- imported_at: optStr2(obj.imported_at) ?? optStr2(frontmatter.source_imported_at),
9052
- external_updated_at: optStr2(obj.external_updated_at) ?? optStr2(frontmatter.source_external_updated_at),
9053
- synced_at: optStr2(obj.synced_at) ?? optStr2(frontmatter.source_synced_at),
9184
+ id: optStr3(obj.id) ?? optStr3(frontmatter.source_id),
9185
+ title: optStr3(obj.title) ?? optStr3(frontmatter.source_title),
9186
+ context: optStr3(obj.context) ?? optStr3(frontmatter.source_context),
9187
+ provider: optStr3(obj.provider) ?? optStr3(frontmatter.source_provider),
9188
+ integration: optStr3(obj.integration) ?? optStr3(frontmatter.source_integration),
9189
+ url: optStr3(obj.url) ?? optStr3(frontmatter.source_url),
9190
+ imported_at: optStr3(obj.imported_at) ?? optStr3(frontmatter.source_imported_at),
9191
+ external_updated_at: optStr3(obj.external_updated_at) ?? optStr3(frontmatter.source_external_updated_at),
9192
+ synced_at: optStr3(obj.synced_at) ?? optStr3(frontmatter.source_synced_at),
9054
9193
  inferred: obj.inferred === true || obj.inferred === "true",
9055
9194
  reason: t && !isValidSource(t) ? `Invalid source type in object: "${t}".` : void 0
9056
9195
  };
@@ -9059,28 +9198,28 @@ function parseWorkItemSource(frontmatter) {
9059
9198
  if (raw && isValidSource(raw)) {
9060
9199
  return {
9061
9200
  type: raw,
9062
- id: optStr2(frontmatter.source_id),
9063
- title: optStr2(frontmatter.source_title),
9064
- context: optStr2(frontmatter.source_context),
9065
- provider: optStr2(frontmatter.source_provider),
9066
- url: optStr2(frontmatter.source_url),
9067
- imported_at: optStr2(frontmatter.source_imported_at),
9068
- external_updated_at: optStr2(frontmatter.source_external_updated_at),
9069
- synced_at: optStr2(frontmatter.source_synced_at),
9201
+ id: optStr3(frontmatter.source_id),
9202
+ title: optStr3(frontmatter.source_title),
9203
+ context: optStr3(frontmatter.source_context),
9204
+ provider: optStr3(frontmatter.source_provider),
9205
+ url: optStr3(frontmatter.source_url),
9206
+ imported_at: optStr3(frontmatter.source_imported_at),
9207
+ external_updated_at: optStr3(frontmatter.source_external_updated_at),
9208
+ synced_at: optStr3(frontmatter.source_synced_at),
9070
9209
  inferred: false
9071
9210
  };
9072
9211
  }
9073
9212
  if (raw && !isValidSource(raw)) {
9074
9213
  return {
9075
9214
  type: "unknown",
9076
- id: optStr2(frontmatter.source_id),
9077
- title: optStr2(frontmatter.source_title),
9078
- context: optStr2(frontmatter.source_context),
9079
- provider: optStr2(frontmatter.source_provider),
9080
- url: optStr2(frontmatter.source_url),
9081
- imported_at: optStr2(frontmatter.source_imported_at),
9082
- external_updated_at: optStr2(frontmatter.source_external_updated_at),
9083
- synced_at: optStr2(frontmatter.source_synced_at),
9215
+ id: optStr3(frontmatter.source_id),
9216
+ title: optStr3(frontmatter.source_title),
9217
+ context: optStr3(frontmatter.source_context),
9218
+ provider: optStr3(frontmatter.source_provider),
9219
+ url: optStr3(frontmatter.source_url),
9220
+ imported_at: optStr3(frontmatter.source_imported_at),
9221
+ external_updated_at: optStr3(frontmatter.source_external_updated_at),
9222
+ synced_at: optStr3(frontmatter.source_synced_at),
9084
9223
  inferred: true,
9085
9224
  reason: `Invalid source value: "${raw}".`
9086
9225
  };
@@ -9088,8 +9227,8 @@ function parseWorkItemSource(frontmatter) {
9088
9227
  if (frontmatter.source_work_item_candidate || frontmatter.source_roadmap_initiative) {
9089
9228
  return {
9090
9229
  type: "roadmap",
9091
- id: optStr2(frontmatter.source_id) ?? optStr2(frontmatter.source_work_item_candidate),
9092
- title: optStr2(frontmatter.source_initiative_title),
9230
+ id: optStr3(frontmatter.source_id) ?? optStr3(frontmatter.source_work_item_candidate),
9231
+ title: optStr3(frontmatter.source_initiative_title),
9093
9232
  inferred: true,
9094
9233
  reason: "Inferred from legacy roadmap fields."
9095
9234
  };
@@ -9097,7 +9236,7 @@ function parseWorkItemSource(frontmatter) {
9097
9236
  if (frontmatter.source_id) {
9098
9237
  return {
9099
9238
  type: "unknown",
9100
- id: optStr2(frontmatter.source_id),
9239
+ id: optStr3(frontmatter.source_id),
9101
9240
  inferred: true,
9102
9241
  reason: "Has source_id but no source type."
9103
9242
  };
@@ -9113,7 +9252,7 @@ function renderSourceCompact(source3) {
9113
9252
  if (source3.id) parts.push(source3.id);
9114
9253
  return parts.join(" \xB7 ");
9115
9254
  }
9116
- function optStr2(v) {
9255
+ function optStr3(v) {
9117
9256
  if (typeof v === "string" && v.trim()) return v.trim();
9118
9257
  return void 0;
9119
9258
  }
@@ -9686,7 +9825,7 @@ function renderRouteCompact(route) {
9686
9825
  }
9687
9826
 
9688
9827
  // ../cli/src/core/metadata-health.ts
9689
- import matter11 from "gray-matter";
9828
+ import matter12 from "gray-matter";
9690
9829
  var KNOWLEDGE_FILES2 = [
9691
9830
  "knowledge/business/business.md",
9692
9831
  "knowledge/product/product.md",
@@ -9706,7 +9845,7 @@ function analyzeMetadataHealth(dir) {
9706
9845
  let data;
9707
9846
  try {
9708
9847
  const raw = readFile(p);
9709
- data = matter11(raw).data;
9848
+ data = matter12(raw).data;
9710
9849
  } catch {
9711
9850
  continue;
9712
9851
  }
@@ -9738,7 +9877,7 @@ function analyzeMetadataHealth(dir) {
9738
9877
  if (isFile(p) && entry.endsWith(".md")) {
9739
9878
  try {
9740
9879
  const raw = readFile(p);
9741
- const fm = matter11(raw).data;
9880
+ const fm = matter12(raw).data;
9742
9881
  if (fm.status === "done") {
9743
9882
  const rel = p.replace(dir + "/", "").replace(dir + "\\", "").replace(/\\/g, "/");
9744
9883
  findings.push({
@@ -10505,7 +10644,7 @@ function renderExplanationAgent(exp) {
10505
10644
  var HISTORY_DIR = ".kaddo/history";
10506
10645
  var RUNS_PATH = `${HISTORY_DIR}/guard-runs.jsonl`;
10507
10646
  var SUMMARY_PATH = `${HISTORY_DIR}/guard-summary.json`;
10508
- function toPosix6(p) {
10647
+ function toPosix7(p) {
10509
10648
  return p.replace(/\\/g, "/");
10510
10649
  }
10511
10650
  function loadGuardRuns(dir) {
@@ -10542,7 +10681,7 @@ function resolveDriftThreads(runs) {
10542
10681
  let resolvedAt;
10543
10682
  for (const r2 of sorted) {
10544
10683
  if (r2.generated_at <= lastWarn) continue;
10545
- const touched = r2.touched_files.some((f3) => toPosix6(f3) === codePath);
10684
+ const touched = r2.touched_files.some((f3) => toPosix7(f3) === codePath);
10546
10685
  const warnedHere = r2.warnings.some((x2) => x2.code_path === codePath);
10547
10686
  const updatedRelated = r2.updated_artifacts.some((a2) => related.has(a2));
10548
10687
  if (touched && updatedRelated && !warnedHere) {
@@ -10759,7 +10898,7 @@ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
10759
10898
  }
10760
10899
  }
10761
10900
  try {
10762
- const body = matter12(readFile(wi.filePath)).content;
10901
+ const body = matter13(readFile(wi.filePath)).content;
10763
10902
  if (hasSection(body, /acceptance|criterios de aceptaci/i)) withAcceptance++;
10764
10903
  else gaps.missing_acceptance_criteria.push(gapItem(wi, "Add an `## Acceptance Criteria` section."));
10765
10904
  if (hasSection(body, /definition of done|^#{1,6}\s*dod\b|definici[oó]n de (terminado|hecho)/i)) withDoD++;
@@ -11752,25 +11891,25 @@ function serializeDriftJson(r2) {
11752
11891
  }
11753
11892
 
11754
11893
  // src/workitems.ts
11755
- import matter13 from "gray-matter";
11894
+ import matter14 from "gray-matter";
11756
11895
  var VALID_SOURCES2 = /* @__PURE__ */ new Set(["manual", "roadmap", "jira", "github", "notion", "xlsx", "csv", "api", "external", "unknown"]);
11757
- function optStr3(v) {
11896
+ function optStr4(v) {
11758
11897
  return typeof v === "string" && v.trim() ? v.trim() : void 0;
11759
11898
  }
11760
11899
  function parseSource(data) {
11761
11900
  const rawSource = data.source;
11762
11901
  if (rawSource != null && typeof rawSource === "object" && !Array.isArray(rawSource)) {
11763
11902
  const obj = rawSource;
11764
- const t = optStr3(obj.type);
11903
+ const t = optStr4(obj.type);
11765
11904
  const type = t && VALID_SOURCES2.has(t) ? t : "unknown";
11766
- return { type, id: optStr3(obj.id) ?? optStr3(data.source_id), title: optStr3(obj.title) ?? optStr3(data.source_title), url: optStr3(obj.url) ?? optStr3(data.source_url), provider: optStr3(obj.provider) ?? optStr3(data.source_provider), inferred: obj.inferred === true || obj.inferred === "true" };
11905
+ return { type, id: optStr4(obj.id) ?? optStr4(data.source_id), title: optStr4(obj.title) ?? optStr4(data.source_title), url: optStr4(obj.url) ?? optStr4(data.source_url), provider: optStr4(obj.provider) ?? optStr4(data.source_provider), inferred: obj.inferred === true || obj.inferred === "true" };
11767
11906
  }
11768
11907
  const raw = rawSource ? String(rawSource) : "";
11769
11908
  if (raw && VALID_SOURCES2.has(raw)) {
11770
- return { type: raw, id: optStr3(data.source_id), title: optStr3(data.source_title), url: optStr3(data.source_url), provider: optStr3(data.source_provider), inferred: false };
11909
+ return { type: raw, id: optStr4(data.source_id), title: optStr4(data.source_title), url: optStr4(data.source_url), provider: optStr4(data.source_provider), inferred: false };
11771
11910
  }
11772
11911
  if (data.source_work_item_candidate || data.source_roadmap_initiative) {
11773
- return { type: "roadmap", id: optStr3(data.source_id) ?? optStr3(data.source_work_item_candidate), title: optStr3(data.source_initiative_title), inferred: true };
11912
+ return { type: "roadmap", id: optStr4(data.source_id) ?? optStr4(data.source_work_item_candidate), title: optStr4(data.source_initiative_title), inferred: true };
11774
11913
  }
11775
11914
  return { type: "unknown", inferred: true };
11776
11915
  }
@@ -11843,7 +11982,7 @@ function listWorkItems(root) {
11843
11982
  if (raw === null) continue;
11844
11983
  let parsed;
11845
11984
  try {
11846
- parsed = matter13(raw);
11985
+ parsed = matter14(raw);
11847
11986
  } catch {
11848
11987
  continue;
11849
11988
  }
@@ -12363,14 +12502,14 @@ function getPrompt(root, name) {
12363
12502
  }
12364
12503
 
12365
12504
  // src/tools.ts
12366
- import matter25 from "gray-matter";
12505
+ import matter26 from "gray-matter";
12367
12506
 
12368
12507
  // ../cli/dist/core.js
12369
12508
  import fs3 from "fs";
12370
12509
  import path3 from "path";
12371
12510
  import { z as z3 } from "zod";
12372
12511
  import { parse as parseYaml10, stringify as stringifyYaml5 } from "yaml";
12373
- import matter14 from "gray-matter";
12512
+ import matter15 from "gray-matter";
12374
12513
  import matter22 from "gray-matter";
12375
12514
  import { parse as parseYaml62, stringify as stringifyYaml42 } from "yaml";
12376
12515
  import { parse as parseYaml22 } from "yaml";
@@ -12381,35 +12520,37 @@ import matter32 from "gray-matter";
12381
12520
  import matter42 from "gray-matter";
12382
12521
  import matter52 from "gray-matter";
12383
12522
  import matter62 from "gray-matter";
12523
+ import matter72 from "gray-matter";
12384
12524
  import { parse as parseYaml72 } from "yaml";
12385
12525
  import { createHash as createHash2 } from "crypto";
12386
- import matter72 from "gray-matter";
12387
12526
  import matter82 from "gray-matter";
12527
+ import matter92 from "gray-matter";
12388
12528
  import { fileURLToPath as fileURLToPath2 } from "url";
12389
12529
  import { dirname as dirname2, join as join22, parse as parse2 } from "path";
12390
12530
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
12391
- import matter92 from "gray-matter";
12392
12531
  import matter102 from "gray-matter";
12393
- import { parse as parseYaml82 } from "yaml";
12394
12532
  import matter112 from "gray-matter";
12395
- import matter122 from "gray-matter";
12533
+ import { parse as parseYaml82 } from "yaml";
12396
12534
  import matter132 from "gray-matter";
12535
+ import matter122 from "gray-matter";
12536
+ import matter142 from "gray-matter";
12537
+ import matter152 from "gray-matter";
12538
+ import matter16 from "gray-matter";
12397
12539
  import { parse as parseYaml92, stringify as stringifyYaml52 } from "yaml";
12398
12540
  import fs4 from "fs";
12399
12541
  import path4 from "path";
12400
12542
  import crypto2 from "crypto";
12401
- import matter142 from "gray-matter";
12543
+ import matter17 from "gray-matter";
12402
12544
  import crypto3 from "crypto";
12403
12545
  import fs5 from "fs";
12404
- import matter15 from "gray-matter";
12405
- import matter16 from "gray-matter";
12406
- import { parse as parseYaml102, stringify as stringifyYaml6 } from "yaml";
12407
- import { parse as parseYaml11, stringify as stringifyYaml7 } from "yaml";
12408
- import matter17 from "gray-matter";
12409
12546
  import matter18 from "gray-matter";
12410
12547
  import matter19 from "gray-matter";
12548
+ import { parse as parseYaml102, stringify as stringifyYaml6 } from "yaml";
12549
+ import { parse as parseYaml11, stringify as stringifyYaml7 } from "yaml";
12411
12550
  import matter20 from "gray-matter";
12412
12551
  import matter21 from "gray-matter";
12552
+ import matter222 from "gray-matter";
12553
+ import matter23 from "gray-matter";
12413
12554
  function exists2(p) {
12414
12555
  return fs3.existsSync(p);
12415
12556
  }
@@ -12585,7 +12726,7 @@ function parseImpactAnalysis2(data) {
12585
12726
  }
12586
12727
  function parseArtifact2(filePath, raw) {
12587
12728
  try {
12588
- const { data } = matter14(raw);
12729
+ const { data } = matter15(raw);
12589
12730
  const globs = Array.isArray(data.code) ? data.code.filter(Boolean) : [];
12590
12731
  return {
12591
12732
  filePath,
@@ -12678,16 +12819,16 @@ function emptyLifecycleCounts2() {
12678
12819
  return Object.fromEntries(LIFECYCLE_STATES3.map((s) => [s, 0]));
12679
12820
  }
12680
12821
  var ARCH_DIR3 = "knowledge";
12681
- function toPosix7(p) {
12822
+ function toPosix8(p) {
12682
12823
  return p.replace(/\\/g, "/");
12683
12824
  }
12684
12825
  function relativeTo2(dir, filePath) {
12685
- const base = toPosix7(dir).replace(/\/+$/, "");
12686
- const full = toPosix7(filePath);
12826
+ const base = toPosix8(dir).replace(/\/+$/, "");
12827
+ const full = toPosix8(filePath);
12687
12828
  return full.startsWith(base + "/") ? full.slice(base.length + 1) : full;
12688
12829
  }
12689
12830
  function isWorkItemArtifact2(a2) {
12690
- return toPosix7(a2.filePath).includes("/delivery/work-items/") && Boolean(a2.type);
12831
+ return toPosix8(a2.filePath).includes("/delivery/work-items/") && Boolean(a2.type);
12691
12832
  }
12692
12833
  function classifyLayer2(posixPath) {
12693
12834
  if (posixPath.includes("/knowledge/business/")) return "business";
@@ -12699,7 +12840,7 @@ function classifyLayer2(posixPath) {
12699
12840
  return "unknown";
12700
12841
  }
12701
12842
  function enrich2(dir, a2) {
12702
- const posix22 = toPosix7(a2.filePath);
12843
+ const posix22 = toPosix8(a2.filePath);
12703
12844
  const isWi = isWorkItemArtifact2(a2);
12704
12845
  return {
12705
12846
  ...a2,
@@ -13960,12 +14101,12 @@ function isValidInitiativeTransition(from, to) {
13960
14101
  function toPosix22(p) {
13961
14102
  return p.replace(/\\/g, "/");
13962
14103
  }
13963
- function optStr4(v) {
14104
+ function optStr5(v) {
13964
14105
  if (typeof v === "string" && v.trim()) return v.trim();
13965
14106
  if (typeof v === "number") return String(v);
13966
14107
  return void 0;
13967
14108
  }
13968
- function strList2(v) {
14109
+ function strList3(v) {
13969
14110
  if (!Array.isArray(v)) return [];
13970
14111
  return v.map((x2) => typeof x2 === "string" ? x2.trim() : String(x2)).filter(Boolean);
13971
14112
  }
@@ -13976,19 +14117,19 @@ function parseCandidates2(data) {
13976
14117
  for (const entry of raw) {
13977
14118
  if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
13978
14119
  const o2 = entry;
13979
- const id = optStr4(o2.id);
13980
- const title = optStr4(o2.title);
14120
+ const id = optStr5(o2.id);
14121
+ const title = optStr5(o2.title);
13981
14122
  if (!id || !title) continue;
13982
14123
  out.push({
13983
14124
  id,
13984
14125
  title,
13985
- type: optStr4(o2.type),
13986
- suggestedKnowledgeLevel: optStr4(o2.suggested_knowledge_level ?? o2.knowledge_level),
13987
- expectedValue: optStr4(o2.expected_value),
13988
- notes: optStr4(o2.notes),
13989
- sourceSignals: strList2(o2.source_signals),
13990
- dependencies: strList2(o2.dependencies),
13991
- materializedAs: optStr4(o2.materialized_as) ?? null
14126
+ type: optStr5(o2.type),
14127
+ suggestedKnowledgeLevel: optStr5(o2.suggested_knowledge_level ?? o2.knowledge_level),
14128
+ expectedValue: optStr5(o2.expected_value),
14129
+ notes: optStr5(o2.notes),
14130
+ sourceSignals: strList3(o2.source_signals),
14131
+ dependencies: strList3(o2.dependencies),
14132
+ materializedAs: optStr5(o2.materialized_as) ?? null
13992
14133
  });
13993
14134
  }
13994
14135
  return out;
@@ -14000,15 +14141,15 @@ function parseExternalLinks2(data) {
14000
14141
  for (const entry of raw) {
14001
14142
  if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
14002
14143
  const o2 = entry;
14003
- const integration = optStr4(o2.integration);
14004
- const externalId = optStr4(o2.external_id);
14144
+ const integration = optStr5(o2.integration);
14145
+ const externalId = optStr5(o2.external_id);
14005
14146
  if (!integration || !externalId) continue;
14006
14147
  out.push({
14007
14148
  integration,
14008
14149
  externalId,
14009
- externalType: optStr4(o2.external_type),
14010
- url: optStr4(o2.url),
14011
- externalStatus: optStr4(o2.external_status)
14150
+ externalType: optStr5(o2.external_type),
14151
+ url: optStr5(o2.url),
14152
+ externalStatus: optStr5(o2.external_status)
14012
14153
  });
14013
14154
  }
14014
14155
  return out;
@@ -14021,23 +14162,26 @@ function parseInitiative2(dir, filePath, raw) {
14021
14162
  return null;
14022
14163
  }
14023
14164
  const data = parsed.data;
14024
- if (optStr4(data.type) !== "initiative") return null;
14025
- const id = optStr4(data.id);
14165
+ if (optStr5(data.type) !== "initiative") return null;
14166
+ const id = optStr5(data.id);
14026
14167
  if (!id) return null;
14027
- const statusRaw = optStr4(data.status) ?? "planned";
14168
+ const statusRaw = optStr5(data.status) ?? "planned";
14028
14169
  const status = isValidInitiativeStatus2(statusRaw) ? statusRaw : "planned";
14029
14170
  return {
14030
14171
  id,
14031
- title: optStr4(data.title) ?? id,
14172
+ title: optStr5(data.title) ?? id,
14032
14173
  status,
14033
- horizon: optStr4(data.horizon) ?? null,
14034
- priority: optStr4(data.priority) ?? null,
14035
- knowledgeLevel: optStr4(data.knowledge_level) ?? null,
14036
- domains: strList2(data.domains),
14037
- relatedCapabilities: strList2(data.related_capabilities),
14038
- source: optStr4(data.source) ?? null,
14039
- sourceId: optStr4(data.source_id) ?? null,
14040
- createdAt: optStr4(data.created_at) ?? null,
14174
+ horizon: optStr5(data.horizon) ?? null,
14175
+ priority: optStr5(data.priority) ?? null,
14176
+ knowledgeLevel: optStr5(data.knowledge_level) ?? null,
14177
+ domains: strList3(data.domains),
14178
+ relatedCapabilities: strList3(data.related_capabilities),
14179
+ source: optStr5(data.source) ?? null,
14180
+ sourceId: optStr5(data.source_id) ?? null,
14181
+ createdAt: optStr5(data.created_at) ?? null,
14182
+ epic: optStr5(data.epic) ?? null,
14183
+ owners: strList3(data.owners),
14184
+ contributors: strList3(data.contributors),
14041
14185
  externalLinks: parseExternalLinks2(data),
14042
14186
  candidates: parseCandidates2(data),
14043
14187
  filePath,
@@ -14139,6 +14283,290 @@ function suggestInitiativesForExternalItem(dir, integration, externalId) {
14139
14283
  )
14140
14284
  );
14141
14285
  }
14286
+ var DELIVERY_PLANS_DIR2 = "knowledge/delivery/plans";
14287
+ var EPICS_DIR2 = "knowledge/delivery/epics";
14288
+ var PLAN_STATES2 = ["draft", "in-progress", "completed", "cancelled"];
14289
+ var EPIC_STATES2 = ["planned", "in-progress", "completed", "cancelled"];
14290
+ function toPosix32(p) {
14291
+ return p.replace(/\\/g, "/");
14292
+ }
14293
+ function optStr22(v) {
14294
+ if (typeof v === "string" && v.trim()) return v.trim();
14295
+ if (typeof v === "number") return String(v);
14296
+ return void 0;
14297
+ }
14298
+ function strList22(v) {
14299
+ if (!Array.isArray(v)) return [];
14300
+ return v.map((x2) => typeof x2 === "string" ? x2.trim() : String(x2)).filter(Boolean);
14301
+ }
14302
+ function relFrom2(dir, filePath) {
14303
+ const d3 = toPosix32(dir);
14304
+ const f3 = toPosix32(filePath);
14305
+ return f3.startsWith(d3 + "/") ? f3.slice(d3.length + 1) : f3;
14306
+ }
14307
+ function parseMilestones2(data) {
14308
+ const raw = data.milestones;
14309
+ if (!Array.isArray(raw)) return [];
14310
+ const out = [];
14311
+ for (const entry of raw) {
14312
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
14313
+ const o2 = entry;
14314
+ const id = optStr22(o2.id);
14315
+ if (!id) continue;
14316
+ out.push({ id, title: optStr22(o2.title) ?? id, exitCriteria: strList22(o2.exit_criteria) });
14317
+ }
14318
+ return out;
14319
+ }
14320
+ function parseDeliveryPlan2(dir, filePath, raw) {
14321
+ let parsed;
14322
+ try {
14323
+ parsed = matter42(raw, {});
14324
+ } catch {
14325
+ return null;
14326
+ }
14327
+ const data = parsed.data;
14328
+ if (optStr22(data.type) !== "delivery-plan") return null;
14329
+ const id = optStr22(data.id);
14330
+ if (!id) return null;
14331
+ const statusRaw = optStr22(data.status) ?? "draft";
14332
+ const status = PLAN_STATES2.includes(statusRaw) ? statusRaw : "draft";
14333
+ return {
14334
+ id,
14335
+ title: optStr22(data.title) ?? id,
14336
+ status,
14337
+ objective: optStr22(data.objective) ?? null,
14338
+ milestones: parseMilestones2(data),
14339
+ dependsOn: strList22(data.depends_on),
14340
+ successCriteria: strList22(data.success_criteria),
14341
+ risks: strList22(data.risks),
14342
+ references: strList22(data.references),
14343
+ filePath,
14344
+ relPath: relFrom2(dir, filePath),
14345
+ body: parsed.content.trim(),
14346
+ rawFrontmatter: data
14347
+ };
14348
+ }
14349
+ function parseEpic2(dir, filePath, raw) {
14350
+ let parsed;
14351
+ try {
14352
+ parsed = matter42(raw, {});
14353
+ } catch {
14354
+ return null;
14355
+ }
14356
+ const data = parsed.data;
14357
+ if (optStr22(data.type) !== "epic") return null;
14358
+ const id = optStr22(data.id);
14359
+ if (!id) return null;
14360
+ const statusRaw = optStr22(data.status) ?? "planned";
14361
+ const status = EPIC_STATES2.includes(statusRaw) ? statusRaw : "planned";
14362
+ return {
14363
+ id,
14364
+ title: optStr22(data.title) ?? id,
14365
+ status,
14366
+ outcome: optStr22(data.outcome ?? data.expected_outcome) ?? null,
14367
+ plan: optStr22(data.plan) ?? null,
14368
+ milestone: optStr22(data.milestone) ?? null,
14369
+ dependsOn: strList22(data.depends_on),
14370
+ successCriteria: strList22(data.success_criteria),
14371
+ relatedCapabilities: strList22(data.related_capabilities),
14372
+ decisions: strList22(data.decisions),
14373
+ filePath,
14374
+ relPath: relFrom2(dir, filePath),
14375
+ body: parsed.content.trim(),
14376
+ rawFrontmatter: data
14377
+ };
14378
+ }
14379
+ function discoverIn2(dir, rel, parse22) {
14380
+ const d3 = join3(dir, rel);
14381
+ if (!exists2(d3)) return [];
14382
+ const out = [];
14383
+ for (const entry of readDir2(d3)) {
14384
+ if (!entry.endsWith(".md")) continue;
14385
+ const full = join3(d3, entry);
14386
+ if (!isFile2(full)) continue;
14387
+ const parsed = parse22(dir, full, readFile2(full));
14388
+ if (parsed) out.push(parsed);
14389
+ }
14390
+ return out;
14391
+ }
14392
+ function discoverDeliveryPlans2(dir) {
14393
+ return discoverIn2(dir, DELIVERY_PLANS_DIR2, parseDeliveryPlan2).sort((a2, b2) => a2.id.localeCompare(b2.id));
14394
+ }
14395
+ function discoverEpics2(dir) {
14396
+ return discoverIn2(dir, EPICS_DIR2, parseEpic2).sort((a2, b2) => a2.id.localeCompare(b2.id));
14397
+ }
14398
+ function getPlan(dir, id) {
14399
+ const want = id.trim().toLowerCase();
14400
+ return discoverDeliveryPlans2(dir).find((p) => p.id.toLowerCase() === want) ?? null;
14401
+ }
14402
+ function getEpic(dir, id) {
14403
+ const want = id.trim().toLowerCase();
14404
+ return discoverEpics2(dir).find((e) => e.id.toLowerCase() === want) ?? null;
14405
+ }
14406
+ function nextId(ids, prefix) {
14407
+ let max = 0;
14408
+ for (const id of ids) {
14409
+ const m2 = id.match(new RegExp(`^${prefix}-(\\d+)$`, "i"));
14410
+ if (m2) max = Math.max(max, Number.parseInt(m2[1], 10));
14411
+ }
14412
+ return `${prefix}-${String(max + 1).padStart(3, "0")}`;
14413
+ }
14414
+ function nextPlanId(dir) {
14415
+ return nextId(discoverDeliveryPlans2(dir).map((p) => p.id), "PLAN");
14416
+ }
14417
+ function nextEpicId(dir) {
14418
+ return nextId(discoverEpics2(dir).map((e) => e.id), "EP");
14419
+ }
14420
+ function epicsInPlan(dir, planId) {
14421
+ return discoverEpics2(dir).filter((e) => e.plan?.toLowerCase() === planId.toLowerCase());
14422
+ }
14423
+ function initiativesInEpic(dir, epicId) {
14424
+ return discoverInitiatives2(dir).filter((i) => i.epic?.toLowerCase() === epicId.toLowerCase());
14425
+ }
14426
+ function wiEpic(wi) {
14427
+ return optStr22(wi.rawFrontmatter?.epic);
14428
+ }
14429
+ function workItemsInEpic(dir, epicId) {
14430
+ const iniIds = new Set(initiativesInEpic(dir, epicId).map((i) => i.id.toLowerCase()));
14431
+ return discoverWorkItems2(dir).filter(
14432
+ (wi) => wiEpic(wi)?.toLowerCase() === epicId.toLowerCase() || wi.initiative && iniIds.has(wi.initiative.toLowerCase())
14433
+ );
14434
+ }
14435
+ function readinessOf(successCriteria, blockingDependencies, delivery) {
14436
+ const committedWorkComplete = delivery.total > 0 && delivery.completed === delivery.total;
14437
+ return {
14438
+ successCriteria,
14439
+ blockingDependencies,
14440
+ committedWorkComplete,
14441
+ // Needs real deliverables, declared criteria and no blocking dependency — never positive otherwise.
14442
+ meetsCompletionPreconditions: committedWorkComplete && successCriteria > 0 && blockingDependencies.length === 0,
14443
+ humanApprovalRequired: true
14444
+ // completion is never automatic
14445
+ };
14446
+ }
14447
+ function deliveryOf(wis, dir) {
14448
+ const byState = emptyLifecycleCounts2();
14449
+ const completedIds = new Set(
14450
+ discoverWorkItems2(dir).filter((w3) => w3.lifecycle === "completed").map((w3) => w3.id.toLowerCase())
14451
+ );
14452
+ let blocked = 0;
14453
+ for (const wi of wis) {
14454
+ const state = wi.lifecycle ?? "ready";
14455
+ byState[state]++;
14456
+ const deps = strList22(wi.rawFrontmatter?.depends_on);
14457
+ if (state !== "completed" && deps.some((d3) => !completedIds.has(d3.toLowerCase()))) blocked++;
14458
+ }
14459
+ return { total: wis.length, byState, blocked, completed: byState.completed };
14460
+ }
14461
+ function computeEpicProgress(dir, epic) {
14462
+ const inis = initiativesInEpic(dir, epic.id);
14463
+ const candidates = inis.reduce((n, i) => n + i.candidates.length, 0);
14464
+ const materialized = inis.reduce((n, i) => n + i.candidates.filter((c) => c.materializedAs).length, 0);
14465
+ const wis = workItemsInEpic(dir, epic.id);
14466
+ const delivery = deliveryOf(wis, dir);
14467
+ return {
14468
+ epic: epic.id,
14469
+ status: epic.status,
14470
+ planning: { initiatives: inis.length, candidates, materialized, remaining: Math.max(0, candidates - materialized) },
14471
+ delivery,
14472
+ readiness: readinessOf(epic.successCriteria.length, unmetDependencies(dir, epic.dependsOn), delivery)
14473
+ };
14474
+ }
14475
+ function computePlanProgress(dir, plan) {
14476
+ const epics = epicsInPlan(dir, plan.id);
14477
+ const byStatus = Object.fromEntries(EPIC_STATES2.map((s) => [s, 0]));
14478
+ for (const e of epics) byStatus[e.status]++;
14479
+ const inis = /* @__PURE__ */ new Map();
14480
+ const wis = /* @__PURE__ */ new Map();
14481
+ for (const e of epics) {
14482
+ for (const i of initiativesInEpic(dir, e.id)) inis.set(i.id, i);
14483
+ for (const w3 of workItemsInEpic(dir, e.id)) wis.set(w3.id, w3);
14484
+ }
14485
+ const candidates = [...inis.values()].reduce((n, i) => n + i.candidates.length, 0);
14486
+ const materialized = [...inis.values()].reduce((n, i) => n + i.candidates.filter((c) => c.materializedAs).length, 0);
14487
+ const delivery = deliveryOf([...wis.values()], dir);
14488
+ return {
14489
+ plan: plan.id,
14490
+ status: plan.status,
14491
+ epics: { total: epics.length, byStatus },
14492
+ planning: { initiatives: inis.size, candidates, materialized, remaining: Math.max(0, candidates - materialized) },
14493
+ delivery,
14494
+ readiness: readinessOf(plan.successCriteria.length, unmetDependencies(dir, plan.dependsOn), delivery),
14495
+ milestones: plan.milestones.map((m2) => ({ id: m2.id, title: m2.title, exitCriteria: m2.exitCriteria.length }))
14496
+ };
14497
+ }
14498
+ function unmetDependencies(dir, deps) {
14499
+ if (deps.length === 0) return [];
14500
+ const done = /* @__PURE__ */ new Set();
14501
+ for (const e of discoverEpics2(dir)) if (e.status === "completed") done.add(e.id.toLowerCase());
14502
+ for (const p of discoverDeliveryPlans2(dir)) if (p.status === "completed") done.add(p.id.toLowerCase());
14503
+ for (const i of discoverInitiatives2(dir)) if (i.status === "completed") done.add(i.id.toLowerCase());
14504
+ return deps.filter((d3) => !done.has(d3.toLowerCase()));
14505
+ }
14506
+ function findCycle(nodes, edges) {
14507
+ const state = /* @__PURE__ */ new Map();
14508
+ const stack = [];
14509
+ const dfs = (n) => {
14510
+ state.set(n, 1);
14511
+ stack.push(n);
14512
+ for (const m2 of edges.get(n) ?? []) {
14513
+ if (!nodes.includes(m2)) continue;
14514
+ const s = state.get(m2) ?? 0;
14515
+ if (s === 1) return [...stack.slice(stack.indexOf(m2)), m2];
14516
+ if (s === 0) {
14517
+ const c = dfs(m2);
14518
+ if (c) return c;
14519
+ }
14520
+ }
14521
+ stack.pop();
14522
+ state.set(n, 2);
14523
+ return null;
14524
+ };
14525
+ for (const n of nodes) if ((state.get(n) ?? 0) === 0) {
14526
+ const c = dfs(n);
14527
+ if (c) return c;
14528
+ }
14529
+ return null;
14530
+ }
14531
+ function validatePlanningGraph(dir) {
14532
+ const plans = discoverDeliveryPlans2(dir);
14533
+ const epics = discoverEpics2(dir);
14534
+ const inis = discoverInitiatives2(dir);
14535
+ const planIds = new Set(plans.map((p) => p.id.toLowerCase()));
14536
+ const epicIds = new Set(epics.map((e) => e.id.toLowerCase()));
14537
+ const iniIds = new Set(inis.map((i) => i.id.toLowerCase()));
14538
+ const findings = [];
14539
+ for (const p of plans) {
14540
+ const seen = /* @__PURE__ */ new Set();
14541
+ for (const m2 of p.milestones) {
14542
+ const key = m2.id.toLowerCase();
14543
+ if (seen.has(key))
14544
+ findings.push({ level: "error", kind: "duplicate_milestone", subject: p.id, message: `Plan ${p.id} declares milestone ${m2.id} more than once.` });
14545
+ seen.add(key);
14546
+ }
14547
+ }
14548
+ for (const e of epics) {
14549
+ if (e.plan && !planIds.has(e.plan.toLowerCase()))
14550
+ findings.push({ level: "error", kind: "invalid_reference", subject: e.id, message: `Epic ${e.id} references missing plan ${e.plan}.` });
14551
+ if (e.plan && e.milestone) {
14552
+ const plan = plans.find((p) => p.id.toLowerCase() === e.plan.toLowerCase());
14553
+ if (plan && !plan.milestones.some((m2) => m2.id.toLowerCase() === e.milestone.toLowerCase()))
14554
+ findings.push({ level: "error", kind: "invalid_reference", subject: e.id, message: `Epic ${e.id} references milestone ${e.milestone} not defined in ${e.plan}.` });
14555
+ }
14556
+ for (const d3 of e.dependsOn) if (!epicIds.has(d3.toLowerCase()))
14557
+ findings.push({ level: "error", kind: "invalid_reference", subject: e.id, message: `Epic ${e.id} depends on missing Epic ${d3}.` });
14558
+ }
14559
+ for (const i of inis) {
14560
+ if (i.epic && !epicIds.has(i.epic.toLowerCase()))
14561
+ findings.push({ level: "error", kind: "invalid_reference", subject: i.id, message: `Initiative ${i.id} references missing Epic ${i.epic}.` });
14562
+ }
14563
+ const epicEdges = new Map(epics.map((e) => [e.id.toLowerCase(), e.dependsOn.map((d3) => d3.toLowerCase())]));
14564
+ const epicCycle = findCycle([...epicIds], epicEdges);
14565
+ if (epicCycle) findings.push({ level: "error", kind: "dependency_cycle", subject: epicCycle[0], message: `Dependency cycle among Epics: ${epicCycle.join(" \u2192 ")}.` });
14566
+ for (const e of epics) for (const d3 of unmetDependencies(dir, e.dependsOn))
14567
+ findings.push({ level: "warning", kind: "pending_dependency", subject: e.id, message: `Epic ${e.id} is blocked by unfinished dependency ${d3}.` });
14568
+ return findings;
14569
+ }
14142
14570
  var RESOURCE_ARTIFACT_TYPE2 = "project-resource";
14143
14571
  var REDACTED_COORDINATE2 = "\u2022\u2022\u2022\u2022 (hidden \u2014 store as an authentication reference)";
14144
14572
  var SECRET_KEY_RE2 = /(pass(word|phrase)?|secret|token|api[_-]?key|\bkey\b|credential|private|auth[_-]?token|access[_-]?key)/i;
@@ -14242,7 +14670,7 @@ function purposeFromBody3(body) {
14242
14670
  return text22 || null;
14243
14671
  }
14244
14672
  function toDetail2(filePath, relPath, raw) {
14245
- const parsed = matter42(raw);
14673
+ const parsed = matter52(raw);
14246
14674
  const fm = parsed.data ?? {};
14247
14675
  const body = parsed.content.trim();
14248
14676
  const id = str3(fm.id) ?? "";
@@ -14458,7 +14886,7 @@ function getDeprecationPlan2(dir, id) {
14458
14886
  );
14459
14887
  if (!match) return null;
14460
14888
  const raw = readFile2(match.filePath);
14461
- const parsed = matter52(raw);
14889
+ const parsed = matter62(raw);
14462
14890
  const fm = parsed.data ?? {};
14463
14891
  const body = parsed.content.trim();
14464
14892
  const targets = parseTargets2(fm.targets);
@@ -14486,14 +14914,14 @@ var ALL_STATUSES2 = ["draft", "ready", "in-progress", "blocked", "completed"];
14486
14914
  function scopeStatuses2(scope) {
14487
14915
  return scope === "all" ? { included: ALL_STATUSES2, excluded: ["archived"] } : { included: ACTIVE_STATUSES2, excluded: ["completed", "archived"] };
14488
14916
  }
14489
- function toPosix32(p) {
14917
+ function toPosix42(p) {
14490
14918
  return p.replace(/\\/g, "/");
14491
14919
  }
14492
14920
  function slug3(s) {
14493
14921
  return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
14494
14922
  }
14495
14923
  function isAdr3(a2) {
14496
- return toPosix32(a2.filePath).includes("/tech/decisions/") && Boolean(a2.type);
14924
+ return toPosix42(a2.filePath).includes("/tech/decisions/") && Boolean(a2.type);
14497
14925
  }
14498
14926
  function buildGraph2(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
14499
14927
  const scope = opts.scope ?? "active";
@@ -14604,6 +15032,27 @@ function buildGraph2(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
14604
15032
  }
14605
15033
  }
14606
15034
  }
15035
+ for (const plan of discoverDeliveryPlans2(dir)) {
15036
+ const planId = `delivery-plan:${slug3(plan.id) || plan.id}`;
15037
+ addNode({ id: planId, type: "delivery-plan", label: `${plan.id} ${plan.title}`.trim(), path: plan.relPath, status: plan.status });
15038
+ for (const m2 of plan.milestones) {
15039
+ const milestoneId = `milestone:${slug3(plan.id)}:${slug3(m2.id) || m2.id}`;
15040
+ addNode({ id: milestoneId, type: "milestone", label: `${m2.id} ${m2.title}`.trim(), path: plan.relPath });
15041
+ addEdge(milestoneId, planId, "belongs_to");
15042
+ }
15043
+ }
15044
+ for (const epic of discoverEpics2(dir)) {
15045
+ const epicNodeId = `epic:${slug3(epic.id) || epic.id}`;
15046
+ addNode({ id: epicNodeId, type: "epic", label: `${epic.id} ${epic.title}`.trim(), path: epic.relPath, status: epic.status });
15047
+ if (epic.plan) {
15048
+ addEdge(epicNodeId, `delivery-plan:${slug3(epic.plan)}`, "belongs_to");
15049
+ if (epic.milestone) addEdge(epicNodeId, `milestone:${slug3(epic.plan)}:${slug3(epic.milestone)}`, "belongs_to");
15050
+ }
15051
+ for (const dep of epic.dependsOn) addEdge(epicNodeId, `epic:${slug3(dep)}`, "depends_on");
15052
+ }
15053
+ for (const ini of discoverInitiatives2(dir)) {
15054
+ if (ini.epic) addEdge(`initiative:${slug3(ini.id) || ini.id}`, `epic:${slug3(ini.epic)}`, "belongs_to");
15055
+ }
14607
15056
  for (const wi of selectedWIs) {
14608
15057
  const id = wi.id || wi.title;
14609
15058
  if (!id || !id.trim()) continue;
@@ -14708,14 +15157,14 @@ function buildGraph2(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
14708
15157
  };
14709
15158
  }
14710
15159
  var KNOWLEDGE32 = "knowledge";
14711
- function toPosix42(p) {
15160
+ function toPosix52(p) {
14712
15161
  return p.replace(/\\/g, "/");
14713
15162
  }
14714
15163
  function slug22(s) {
14715
15164
  return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
14716
15165
  }
14717
15166
  function isAdr22(a2) {
14718
- return toPosix42(a2.filePath).includes("/tech/decisions/") && Boolean(a2.type);
15167
+ return toPosix52(a2.filePath).includes("/tech/decisions/") && Boolean(a2.type);
14719
15168
  }
14720
15169
  function capabilityHeadings2(dir) {
14721
15170
  const p = join3(dir, KNOWLEDGE32, "product", "capabilities.md");
@@ -15048,7 +15497,7 @@ function reportFiles2(dir) {
15048
15497
  const match = name.match(reportName2);
15049
15498
  if (!match) return [];
15050
15499
  try {
15051
- const parsed = matter72(readFile2(join3(delivery, name)));
15500
+ const parsed = matter82(readFile2(join3(delivery, name)));
15052
15501
  const fingerprint = typeof parsed.data.source_fingerprint === "string" ? parsed.data.source_fingerprint : "";
15053
15502
  const manifest = parsed.data.source_manifest;
15054
15503
  const sourceManifest = manifest && typeof manifest === "object" && !Array.isArray(manifest) ? Object.fromEntries(Object.entries(manifest).filter(([, digest]) => typeof digest === "string")) : {};
@@ -15147,7 +15596,7 @@ function canonicalReport(context, proposal) {
15147
15596
  source_manifest: Object.fromEntries(context.sources.map((item) => [item.path, item.digest]))
15148
15597
  };
15149
15598
  if (supersedes) data.supersedes = supersedes;
15150
- return matter72.stringify(redactSecrets2(proposal).trimEnd() + "\n", data);
15599
+ return matter82.stringify(redactSecrets2(proposal).trimEnd() + "\n", data);
15151
15600
  }
15152
15601
  function persistPocReport(dir, proposal, opts = {}) {
15153
15602
  const context = buildPocReportContext2(dir);
@@ -16111,9 +16560,9 @@ var RESPONSIBILITY_MATRIX2 = {
16111
16560
  },
16112
16561
  "roadmap-agent": {
16113
16562
  agent: "roadmap-agent",
16114
- responsibleFor: ["Roadmap", "Initiatives", "Work Item candidates"],
16563
+ responsibleFor: ["Roadmap", "Initiatives", "Work Item candidates", "Delivery Plans and Epics (optional planning layer)"],
16115
16564
  produces: ["knowledge/delivery/roadmap.md"],
16116
- canSuggest: ["kaddo create --from roadmap", "work-item-agent"],
16565
+ canSuggest: ["kaddo create --from roadmap", "work-item-agent", "kaddo plan create", "kaddo epic create"],
16117
16566
  cannotSuggest: ["branches", "commits", "pull requests", "code"],
16118
16567
  next: ["kaddo create --from roadmap", "work-item-agent"]
16119
16568
  },
@@ -18616,19 +19065,19 @@ function parseWorkItemSource2(frontmatter2) {
18616
19065
  const rawSource = frontmatter2.source;
18617
19066
  if (rawSource != null && typeof rawSource === "object" && !Array.isArray(rawSource)) {
18618
19067
  const obj = rawSource;
18619
- const t = optStr22(obj.type);
19068
+ const t = optStr32(obj.type);
18620
19069
  const type = t && isValidSource2(t) ? t : "unknown";
18621
19070
  return {
18622
19071
  type,
18623
- id: optStr22(obj.id) ?? optStr22(frontmatter2.source_id),
18624
- title: optStr22(obj.title) ?? optStr22(frontmatter2.source_title),
18625
- context: optStr22(obj.context) ?? optStr22(frontmatter2.source_context),
18626
- provider: optStr22(obj.provider) ?? optStr22(frontmatter2.source_provider),
18627
- integration: optStr22(obj.integration) ?? optStr22(frontmatter2.source_integration),
18628
- url: optStr22(obj.url) ?? optStr22(frontmatter2.source_url),
18629
- imported_at: optStr22(obj.imported_at) ?? optStr22(frontmatter2.source_imported_at),
18630
- external_updated_at: optStr22(obj.external_updated_at) ?? optStr22(frontmatter2.source_external_updated_at),
18631
- synced_at: optStr22(obj.synced_at) ?? optStr22(frontmatter2.source_synced_at),
19072
+ id: optStr32(obj.id) ?? optStr32(frontmatter2.source_id),
19073
+ title: optStr32(obj.title) ?? optStr32(frontmatter2.source_title),
19074
+ context: optStr32(obj.context) ?? optStr32(frontmatter2.source_context),
19075
+ provider: optStr32(obj.provider) ?? optStr32(frontmatter2.source_provider),
19076
+ integration: optStr32(obj.integration) ?? optStr32(frontmatter2.source_integration),
19077
+ url: optStr32(obj.url) ?? optStr32(frontmatter2.source_url),
19078
+ imported_at: optStr32(obj.imported_at) ?? optStr32(frontmatter2.source_imported_at),
19079
+ external_updated_at: optStr32(obj.external_updated_at) ?? optStr32(frontmatter2.source_external_updated_at),
19080
+ synced_at: optStr32(obj.synced_at) ?? optStr32(frontmatter2.source_synced_at),
18632
19081
  inferred: obj.inferred === true || obj.inferred === "true",
18633
19082
  reason: t && !isValidSource2(t) ? `Invalid source type in object: "${t}".` : void 0
18634
19083
  };
@@ -18637,28 +19086,28 @@ function parseWorkItemSource2(frontmatter2) {
18637
19086
  if (raw && isValidSource2(raw)) {
18638
19087
  return {
18639
19088
  type: raw,
18640
- id: optStr22(frontmatter2.source_id),
18641
- title: optStr22(frontmatter2.source_title),
18642
- context: optStr22(frontmatter2.source_context),
18643
- provider: optStr22(frontmatter2.source_provider),
18644
- url: optStr22(frontmatter2.source_url),
18645
- imported_at: optStr22(frontmatter2.source_imported_at),
18646
- external_updated_at: optStr22(frontmatter2.source_external_updated_at),
18647
- synced_at: optStr22(frontmatter2.source_synced_at),
19089
+ id: optStr32(frontmatter2.source_id),
19090
+ title: optStr32(frontmatter2.source_title),
19091
+ context: optStr32(frontmatter2.source_context),
19092
+ provider: optStr32(frontmatter2.source_provider),
19093
+ url: optStr32(frontmatter2.source_url),
19094
+ imported_at: optStr32(frontmatter2.source_imported_at),
19095
+ external_updated_at: optStr32(frontmatter2.source_external_updated_at),
19096
+ synced_at: optStr32(frontmatter2.source_synced_at),
18648
19097
  inferred: false
18649
19098
  };
18650
19099
  }
18651
19100
  if (raw && !isValidSource2(raw)) {
18652
19101
  return {
18653
19102
  type: "unknown",
18654
- id: optStr22(frontmatter2.source_id),
18655
- title: optStr22(frontmatter2.source_title),
18656
- context: optStr22(frontmatter2.source_context),
18657
- provider: optStr22(frontmatter2.source_provider),
18658
- url: optStr22(frontmatter2.source_url),
18659
- imported_at: optStr22(frontmatter2.source_imported_at),
18660
- external_updated_at: optStr22(frontmatter2.source_external_updated_at),
18661
- synced_at: optStr22(frontmatter2.source_synced_at),
19103
+ id: optStr32(frontmatter2.source_id),
19104
+ title: optStr32(frontmatter2.source_title),
19105
+ context: optStr32(frontmatter2.source_context),
19106
+ provider: optStr32(frontmatter2.source_provider),
19107
+ url: optStr32(frontmatter2.source_url),
19108
+ imported_at: optStr32(frontmatter2.source_imported_at),
19109
+ external_updated_at: optStr32(frontmatter2.source_external_updated_at),
19110
+ synced_at: optStr32(frontmatter2.source_synced_at),
18662
19111
  inferred: true,
18663
19112
  reason: `Invalid source value: "${raw}".`
18664
19113
  };
@@ -18666,8 +19115,8 @@ function parseWorkItemSource2(frontmatter2) {
18666
19115
  if (frontmatter2.source_work_item_candidate || frontmatter2.source_roadmap_initiative) {
18667
19116
  return {
18668
19117
  type: "roadmap",
18669
- id: optStr22(frontmatter2.source_id) ?? optStr22(frontmatter2.source_work_item_candidate),
18670
- title: optStr22(frontmatter2.source_initiative_title),
19118
+ id: optStr32(frontmatter2.source_id) ?? optStr32(frontmatter2.source_work_item_candidate),
19119
+ title: optStr32(frontmatter2.source_initiative_title),
18671
19120
  inferred: true,
18672
19121
  reason: "Inferred from legacy roadmap fields."
18673
19122
  };
@@ -18675,7 +19124,7 @@ function parseWorkItemSource2(frontmatter2) {
18675
19124
  if (frontmatter2.source_id) {
18676
19125
  return {
18677
19126
  type: "unknown",
18678
- id: optStr22(frontmatter2.source_id),
19127
+ id: optStr32(frontmatter2.source_id),
18679
19128
  inferred: true,
18680
19129
  reason: "Has source_id but no source type."
18681
19130
  };
@@ -18686,7 +19135,7 @@ function parseWorkItemSource2(frontmatter2) {
18686
19135
  reason: "No source metadata found."
18687
19136
  };
18688
19137
  }
18689
- function optStr22(v) {
19138
+ function optStr32(v) {
18690
19139
  if (typeof v === "string" && v.trim()) return v.trim();
18691
19140
  return void 0;
18692
19141
  }
@@ -18933,17 +19382,387 @@ var LEGACY_STEPS2 = [
18933
19382
  var HISTORY_DIR2 = ".kaddo/history";
18934
19383
  var RUNS_PATH2 = `${HISTORY_DIR2}/guard-runs.jsonl`;
18935
19384
  var SUMMARY_PATH2 = `${HISTORY_DIR2}/guard-summary.json`;
18936
- var USER_FACING_KEYWORDS_EN = [
18937
- "user",
18938
- "visitor",
18939
- "page",
18940
- "registration",
18941
- "login",
18942
- "form",
18943
- "show",
18944
- "display",
18945
- "cta",
18946
- "flow",
19385
+ var WORK_ITEMS_DIR = "knowledge/delivery/work-items";
19386
+ var DRAFT_DIR = `${WORK_ITEMS_DIR}/draft`;
19387
+ var InitiativeWriteError = class extends Error {
19388
+ constructor(message) {
19389
+ super(message);
19390
+ this.name = "InitiativeWriteError";
19391
+ }
19392
+ };
19393
+ function slugify22(s) {
19394
+ return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 50);
19395
+ }
19396
+ function today2() {
19397
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
19398
+ }
19399
+ function candidateToData(c) {
19400
+ const out = { id: c.id, title: c.title };
19401
+ if (c.type) out.type = c.type;
19402
+ if (c.suggestedKnowledgeLevel) out.suggested_knowledge_level = c.suggestedKnowledgeLevel;
19403
+ if (c.expectedValue) out.expected_value = c.expectedValue;
19404
+ if (c.notes) out.notes = c.notes;
19405
+ if (c.sourceSignals.length) out.source_signals = c.sourceSignals;
19406
+ if (c.dependencies.length) out.dependencies = c.dependencies;
19407
+ if (c.materializedAs) out.materialized_as = c.materializedAs;
19408
+ return out;
19409
+ }
19410
+ function externalLinkToData(l2) {
19411
+ const out = { integration: l2.integration, external_id: l2.externalId };
19412
+ if (l2.externalType) out.external_type = l2.externalType;
19413
+ if (l2.url) out.url = l2.url;
19414
+ if (l2.externalStatus) out.external_status = l2.externalStatus;
19415
+ return out;
19416
+ }
19417
+ function normalizeCandidate(input, fallbackId) {
19418
+ return {
19419
+ id: input.id?.trim() || fallbackId,
19420
+ title: input.title.trim(),
19421
+ type: input.type?.trim() || void 0,
19422
+ suggestedKnowledgeLevel: input.suggestedKnowledgeLevel?.trim() || void 0,
19423
+ expectedValue: input.expectedValue?.trim() || void 0,
19424
+ notes: input.notes?.trim() || void 0,
19425
+ sourceSignals: input.sourceSignals ?? [],
19426
+ dependencies: input.dependencies ?? [],
19427
+ materializedAs: input.materializedAs ?? null
19428
+ };
19429
+ }
19430
+ function defaultBody(title) {
19431
+ return [
19432
+ `# ${title}`,
19433
+ "",
19434
+ "## Goal",
19435
+ "",
19436
+ "_What outcome does this Initiative pursue?_",
19437
+ "",
19438
+ "## Expected Value",
19439
+ "",
19440
+ "_Why it matters._",
19441
+ "",
19442
+ "## Scope",
19443
+ "",
19444
+ "_What this Initiative covers._",
19445
+ "",
19446
+ "## Out of Scope",
19447
+ "",
19448
+ "_What it explicitly does not cover._",
19449
+ "",
19450
+ "## Success Criteria",
19451
+ "",
19452
+ '_Observable outcomes that define completion (not just "all Work Items done")._',
19453
+ "",
19454
+ "## Dependencies",
19455
+ "",
19456
+ "_Other Initiatives, modules, or external work this depends on._",
19457
+ "",
19458
+ "## Work Item Candidates",
19459
+ "",
19460
+ "_Candidates live in frontmatter `candidates`; summarize them here as they evolve._",
19461
+ "",
19462
+ "## Open Questions",
19463
+ "",
19464
+ "_Unresolved questions for this Initiative._",
19465
+ "",
19466
+ "## Learning",
19467
+ "",
19468
+ "_Captured on completion: what was delivered, what stayed out of scope, outcome reached._",
19469
+ ""
19470
+ ].join("\n");
19471
+ }
19472
+ function buildInitiativeContent(id, input) {
19473
+ const status = input.status ?? "planned";
19474
+ if (!isValidInitiativeStatus2(status)) {
19475
+ throw new InitiativeWriteError(`Invalid initiative status: ${status}.`);
19476
+ }
19477
+ const candidates = (input.candidates ?? []).map(
19478
+ (c, i) => normalizeCandidate(c, `WI-CANDIDATE-${String(i + 1).padStart(3, "0")}`)
19479
+ );
19480
+ const data = {
19481
+ type: "initiative",
19482
+ id,
19483
+ title: input.title.trim(),
19484
+ status,
19485
+ knowledge_level: input.knowledgeLevel ?? "K2",
19486
+ domains: input.domains ?? [],
19487
+ related_capabilities: input.relatedCapabilities ?? [],
19488
+ created_at: today2(),
19489
+ external_links: (input.externalLinks ?? []).map(externalLinkToData),
19490
+ candidates: candidates.map(candidateToData)
19491
+ };
19492
+ if (input.horizon) data.horizon = input.horizon;
19493
+ if (input.priority) data.priority = input.priority;
19494
+ if (input.source) data.source = input.source;
19495
+ if (input.sourceId) data.source_id = input.sourceId;
19496
+ if (input.epic) data.epic = input.epic.trim();
19497
+ if (input.owners?.length) data.owners = input.owners.map((o2) => o2.trim()).filter(Boolean);
19498
+ if (input.contributors?.length) data.contributors = input.contributors.map((c) => c.trim()).filter(Boolean);
19499
+ return matter122.stringify("\n" + (input.body ?? defaultBody(input.title.trim())), data);
19500
+ }
19501
+ function createInitiative(dir, input) {
19502
+ if (!input.title?.trim()) throw new InitiativeWriteError("Initiative title is required.");
19503
+ const id = nextInitiativeId(dir);
19504
+ const fileName = `${id}-${slugify22(input.title)}.md`;
19505
+ const iniDir = join3(dir, INITIATIVES_DIR2);
19506
+ ensureDir2(iniDir);
19507
+ const filePath = join3(iniDir, fileName);
19508
+ const content = buildInitiativeContent(id, input);
19509
+ writeFile2(filePath, content);
19510
+ const ini = parseInitiative2(dir, filePath, content);
19511
+ if (!ini) throw new InitiativeWriteError("Failed to parse the Initiative just written.");
19512
+ return ini;
19513
+ }
19514
+ function transitionInitiative(dir, id, to) {
19515
+ const ini = getInitiative(dir, id);
19516
+ if (!ini) throw new InitiativeWriteError(`Initiative ${id} not found.`);
19517
+ if (!isValidInitiativeStatus2(to)) throw new InitiativeWriteError(`Invalid status: ${to}.`);
19518
+ if (ini.status === to) return ini;
19519
+ if (!isValidInitiativeTransition(ini.status, to)) {
19520
+ throw new InitiativeWriteError(`Invalid transition ${ini.status} \u2192 ${to}.`);
19521
+ }
19522
+ const raw = readFile2(ini.filePath);
19523
+ const { data, content } = matter122(raw, {});
19524
+ data.status = to;
19525
+ if (to === "completed") data.completed_at = today2();
19526
+ const newContent = matter122.stringify(content, data);
19527
+ writeFile2(ini.filePath, newContent);
19528
+ const updated = parseInitiative2(dir, ini.filePath, newContent);
19529
+ if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after transition.");
19530
+ return updated;
19531
+ }
19532
+ function addCandidate(dir, id, candidate) {
19533
+ const ini = getInitiative(dir, id);
19534
+ if (!ini) throw new InitiativeWriteError(`Initiative ${id} not found.`);
19535
+ const raw = readFile2(ini.filePath);
19536
+ const { data, content } = matter122(raw, {});
19537
+ const existing = Array.isArray(data.candidates) ? data.candidates : [];
19538
+ const fallbackId = `WI-CANDIDATE-${String(existing.length + 1).padStart(3, "0")}`;
19539
+ const normalized = normalizeCandidate(candidate, fallbackId);
19540
+ if (ini.candidates.some((c) => c.id === normalized.id)) {
19541
+ throw new InitiativeWriteError(`Candidate ${normalized.id} already exists in ${id}.`);
19542
+ }
19543
+ data.candidates = [...existing, candidateToData(normalized)];
19544
+ const newContent = matter122.stringify(content, data);
19545
+ writeFile2(ini.filePath, newContent);
19546
+ const updated = parseInitiative2(dir, ini.filePath, newContent);
19547
+ if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after adding a candidate.");
19548
+ return updated;
19549
+ }
19550
+ function updateInitiative(dir, id, patch) {
19551
+ const ini = getInitiative(dir, id);
19552
+ if (!ini) throw new InitiativeWriteError(`Initiative ${id} not found.`);
19553
+ const raw = readFile2(ini.filePath);
19554
+ const { data, content } = matter122(raw, {});
19555
+ if (patch.title !== void 0) {
19556
+ if (!patch.title.trim()) throw new InitiativeWriteError("Initiative title cannot be empty.");
19557
+ data.title = patch.title.trim();
19558
+ }
19559
+ if (patch.horizon !== void 0) data.horizon = patch.horizon;
19560
+ if (patch.priority !== void 0) data.priority = patch.priority;
19561
+ if (patch.knowledgeLevel !== void 0) data.knowledge_level = patch.knowledgeLevel;
19562
+ if (patch.domains !== void 0) data.domains = patch.domains;
19563
+ if (patch.relatedCapabilities !== void 0) data.related_capabilities = patch.relatedCapabilities;
19564
+ if (patch.epic !== void 0) {
19565
+ if (patch.epic === null || patch.epic.trim() === "") delete data.epic;
19566
+ else data.epic = patch.epic.trim();
19567
+ }
19568
+ if (patch.owners !== void 0) data.owners = patch.owners.map((o2) => o2.trim()).filter(Boolean);
19569
+ if (patch.contributors !== void 0) data.contributors = patch.contributors.map((c) => c.trim()).filter(Boolean);
19570
+ const newContent = matter122.stringify(content, data);
19571
+ writeFile2(ini.filePath, newContent);
19572
+ const updated = parseInitiative2(dir, ini.filePath, newContent);
19573
+ if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after update.");
19574
+ return updated;
19575
+ }
19576
+ function addExternalLink(dir, id, link) {
19577
+ const ini = getInitiative(dir, id);
19578
+ if (!ini) throw new InitiativeWriteError(`Initiative ${id} not found.`);
19579
+ if (!link.integration?.trim() || !link.externalId?.trim()) {
19580
+ throw new InitiativeWriteError("External link requires integration and externalId.");
19581
+ }
19582
+ if (ini.externalLinks.some(
19583
+ (l2) => l2.integration.trim().toLowerCase() === link.integration.trim().toLowerCase() && l2.externalId.trim().toLowerCase() === link.externalId.trim().toLowerCase()
19584
+ )) {
19585
+ throw new InitiativeWriteError(`External link ${link.integration}:${link.externalId} already exists on ${id}.`);
19586
+ }
19587
+ const raw = readFile2(ini.filePath);
19588
+ const { data, content } = matter122(raw, {});
19589
+ const existing = Array.isArray(data.external_links) ? data.external_links : [];
19590
+ data.external_links = [...existing, externalLinkToData(link)];
19591
+ const newContent = matter122.stringify(content, data);
19592
+ writeFile2(ini.filePath, newContent);
19593
+ const updated = parseInitiative2(dir, ini.filePath, newContent);
19594
+ if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after adding an external link.");
19595
+ return updated;
19596
+ }
19597
+ function nextWorkItemId(dir) {
19598
+ const wiDir = join3(dir, WORK_ITEMS_DIR);
19599
+ let max = 0;
19600
+ const walk2 = (d3) => {
19601
+ if (!exists2(d3)) return;
19602
+ for (const entry of readDir2(d3)) {
19603
+ const full = join3(d3, entry);
19604
+ if (isFile2(full)) {
19605
+ const m2 = entry.match(/WI-(\d+)/);
19606
+ if (m2) max = Math.max(max, parseInt(m2[1], 10));
19607
+ } else if (!entry.startsWith(".")) {
19608
+ walk2(full);
19609
+ }
19610
+ }
19611
+ };
19612
+ walk2(wiDir);
19613
+ return `WI-${String(max + 1).padStart(3, "0")}`;
19614
+ }
19615
+ function buildWorkItemContent(wiId, iniId, candidate) {
19616
+ const data = {
19617
+ type: candidate.type ?? "feature",
19618
+ id: wiId,
19619
+ title: candidate.title,
19620
+ knowledge_level: candidate.suggestedKnowledgeLevel ?? "K2",
19621
+ status: "draft",
19622
+ phase: "now",
19623
+ initiative: iniId,
19624
+ domains: [],
19625
+ code: [],
19626
+ created_at: today2(),
19627
+ source: "initiative",
19628
+ source_id: candidate.id,
19629
+ source_initiative: iniId,
19630
+ affected_modules: []
19631
+ };
19632
+ if (candidate.sourceSignals.length) data.source_signals = candidate.sourceSignals;
19633
+ if (candidate.dependencies.length) data.dependencies = candidate.dependencies;
19634
+ const body = [
19635
+ `# ${candidate.title}`,
19636
+ "",
19637
+ `> Materialized from Initiative ${iniId}, candidate ${candidate.id}.`,
19638
+ "",
19639
+ "## Problem",
19640
+ "",
19641
+ candidate.expectedValue ? `_Expected value:_ ${candidate.expectedValue}` : "_Describe the problem._",
19642
+ "",
19643
+ "## Acceptance Criteria",
19644
+ "",
19645
+ "- [ ] _Define at least one testable criterion._",
19646
+ "",
19647
+ "## Out of scope",
19648
+ "",
19649
+ "_Not included in this Work Item._",
19650
+ "",
19651
+ "## Validation",
19652
+ "",
19653
+ "_How will this be validated?_",
19654
+ "",
19655
+ "## Learning",
19656
+ "",
19657
+ "_What did we learn? Update after completion._",
19658
+ ""
19659
+ ].join("\n");
19660
+ return matter122.stringify("\n" + body, data);
19661
+ }
19662
+ function materializeCandidate(dir, id, candidateId) {
19663
+ const ini = getInitiative(dir, id);
19664
+ if (!ini) throw new InitiativeWriteError(`Initiative ${id} not found.`);
19665
+ const candidate = ini.candidates.find((c) => c.id === candidateId);
19666
+ if (!candidate) throw new InitiativeWriteError(`Candidate ${candidateId} not found in ${id}.`);
19667
+ if (candidate.materializedAs) {
19668
+ throw new InitiativeWriteError(
19669
+ `Candidate ${candidateId} is already materialized as ${candidate.materializedAs}.`
19670
+ );
19671
+ }
19672
+ const wiId = nextWorkItemId(dir);
19673
+ const fileName = `${wiId}-${slugify22(candidate.title)}.md`;
19674
+ ensureDir2(join3(dir, DRAFT_DIR));
19675
+ writeFile2(join3(dir, DRAFT_DIR, fileName), buildWorkItemContent(wiId, ini.id, candidate));
19676
+ const raw = readFile2(ini.filePath);
19677
+ const { data, content } = matter122(raw, {});
19678
+ const cands = Array.isArray(data.candidates) ? data.candidates : [];
19679
+ for (const c of cands) {
19680
+ if (c && typeof c === "object" && String(c.id) === candidateId) c.materialized_as = wiId;
19681
+ }
19682
+ data.candidates = cands;
19683
+ const newContent = matter122.stringify(content, data);
19684
+ writeFile2(ini.filePath, newContent);
19685
+ const updated = parseInitiative2(dir, ini.filePath, newContent);
19686
+ if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after materialization.");
19687
+ return { workItemId: wiId, fileName, initiative: updated };
19688
+ }
19689
+ var DeliveryPlanningWriteError = class extends Error {
19690
+ constructor(message) {
19691
+ super(message);
19692
+ this.name = "DeliveryPlanningWriteError";
19693
+ }
19694
+ };
19695
+ function slugify3(s) {
19696
+ return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "item";
19697
+ }
19698
+ function createDeliveryPlan(dir, input) {
19699
+ if (!input.title?.trim()) throw new DeliveryPlanningWriteError("Plan title is required.");
19700
+ const status = input.status ?? "draft";
19701
+ if (!PLAN_STATES2.includes(status)) throw new DeliveryPlanningWriteError(`Invalid plan status: ${status}.`);
19702
+ const id = nextPlanId(dir);
19703
+ const data = { type: "delivery-plan", id, title: input.title.trim(), status, milestones: [] };
19704
+ if (input.objective?.trim()) data.objective = input.objective.trim();
19705
+ const body = `
19706
+ # ${input.title.trim()}
19707
+
19708
+ _Describe the objective, scope, exclusions, milestones, dependencies, success criteria and risks._
19709
+ `;
19710
+ const filePath = join3(dir, DELIVERY_PLANS_DIR2, `${id}-${slugify3(input.title)}.md`);
19711
+ ensureDir2(join3(dir, DELIVERY_PLANS_DIR2));
19712
+ const content = matter132.stringify(body, data);
19713
+ writeFile2(filePath, content);
19714
+ const plan = parseDeliveryPlan2(dir, filePath, content);
19715
+ if (!plan) throw new DeliveryPlanningWriteError("Failed to parse the Plan just written.");
19716
+ return plan;
19717
+ }
19718
+ function createEpic(dir, input) {
19719
+ if (!input.title?.trim()) throw new DeliveryPlanningWriteError("Epic title is required.");
19720
+ const status = input.status ?? "planned";
19721
+ if (!EPIC_STATES2.includes(status)) throw new DeliveryPlanningWriteError(`Invalid epic status: ${status}.`);
19722
+ if (input.plan) {
19723
+ const plan = getPlan(dir, input.plan);
19724
+ if (!plan) throw new DeliveryPlanningWriteError(`Plan ${input.plan} does not exist.`);
19725
+ if (input.milestone && !plan.milestones.some((m2) => m2.id.toLowerCase() === input.milestone.toLowerCase())) {
19726
+ throw new DeliveryPlanningWriteError(`Milestone ${input.milestone} is not defined in ${input.plan}.`);
19727
+ }
19728
+ } else if (input.milestone) {
19729
+ throw new DeliveryPlanningWriteError("A milestone can only be set together with its plan.");
19730
+ }
19731
+ const id = nextEpicId(dir);
19732
+ const data = { type: "epic", id, title: input.title.trim(), status };
19733
+ if (input.outcome?.trim()) data.outcome = input.outcome.trim();
19734
+ if (input.plan) data.plan = input.plan;
19735
+ if (input.milestone) data.milestone = input.milestone;
19736
+ const body = `
19737
+ # ${input.title.trim()}
19738
+
19739
+ _Describe the expected outcome, success criteria, dependencies and delivery evidence._
19740
+ `;
19741
+ const filePath = join3(dir, EPICS_DIR2, `${id}-${slugify3(input.title)}.md`);
19742
+ ensureDir2(join3(dir, EPICS_DIR2));
19743
+ const content = matter132.stringify(body, data);
19744
+ writeFile2(filePath, content);
19745
+ const epic = parseEpic2(dir, filePath, content);
19746
+ if (!epic) throw new DeliveryPlanningWriteError("Failed to parse the Epic just written.");
19747
+ return epic;
19748
+ }
19749
+ function associateInitiativeWithEpic(dir, initiativeId, epicId) {
19750
+ const ini = getInitiative(dir, initiativeId);
19751
+ if (!ini) throw new DeliveryPlanningWriteError(`Initiative ${initiativeId} not found.`);
19752
+ if (!getEpic(dir, epicId)) throw new DeliveryPlanningWriteError(`Epic ${epicId} does not exist.`);
19753
+ return updateInitiative(dir, ini.id, { epic: epicId });
19754
+ }
19755
+ var USER_FACING_KEYWORDS_EN = [
19756
+ "user",
19757
+ "visitor",
19758
+ "page",
19759
+ "registration",
19760
+ "login",
19761
+ "form",
19762
+ "show",
19763
+ "display",
19764
+ "cta",
19765
+ "flow",
18947
19766
  "screen",
18948
19767
  "dashboard",
18949
19768
  "ui",
@@ -19107,7 +19926,7 @@ var PROVENANCE = /* @__PURE__ */ new Set(["declared", "derived", "agent-reviewed
19107
19926
  function isRelative(p) {
19108
19927
  return typeof p === "string" && p.trim() !== "" && !/^([a-zA-Z]:[\\/]|\/)/.test(p) && !p.includes("..");
19109
19928
  }
19110
- function strList22(v) {
19929
+ function strList32(v) {
19111
19930
  return Array.isArray(v) ? v.map((x2) => typeof x2 === "string" ? x2.trim() : "").filter(Boolean) : [];
19112
19931
  }
19113
19932
  function loadSystemTopology(dir) {
@@ -19148,7 +19967,7 @@ function parseTopologyContent(raw, dir) {
19148
19967
  const moduleRaw = typeof e.module === "string" ? e.module.trim() : void 0;
19149
19968
  if (moduleRaw && !validModules.has(moduleRaw)) findings.push({ level: "warning", message: `Entity "${id}" references unregistered module "${moduleRaw}".` });
19150
19969
  const rawImpl = e.implementation ?? e.implementation_refs;
19151
- const implementation = strList22(rawImpl).filter((p) => {
19970
+ const implementation = strList32(rawImpl).filter((p) => {
19152
19971
  if (isRelative(p)) return true;
19153
19972
  findings.push({ level: "warning", message: `Entity "${id}" implementation ref "${p}" is not a safe relative path and was dropped.` });
19154
19973
  return false;
@@ -19156,7 +19975,7 @@ function parseTopologyContent(raw, dir) {
19156
19975
  const prov = e.provenance;
19157
19976
  const provOrigin = typeof prov === "string" ? prov : prov && typeof prov === "object" && typeof prov.origin === "string" ? String(prov.origin) : void 0;
19158
19977
  const provenance = provOrigin && PROVENANCE.has(provOrigin) ? provOrigin : void 0;
19159
- const evidence = strList22(e.evidence ?? (prov && typeof prov === "object" ? prov.evidence_refs : void 0)).filter(isRelative);
19978
+ const evidence = strList32(e.evidence ?? (prov && typeof prov === "object" ? prov.evidence_refs : void 0)).filter(isRelative);
19160
19979
  seenIds.add(id);
19161
19980
  entities.push({
19162
19981
  id,
@@ -19165,7 +19984,7 @@ function parseTopologyContent(raw, dir) {
19165
19984
  ...typeof e.purpose === "string" && e.purpose.trim() ? { purpose: e.purpose.trim() } : {},
19166
19985
  ...moduleRaw && validModules.has(moduleRaw) ? { moduleId: moduleRaw } : {},
19167
19986
  implementationRefs: implementation,
19168
- knowledgeRefs: strList22(e.knowledge ?? e.knowledge_refs),
19987
+ knowledgeRefs: strList32(e.knowledge ?? e.knowledge_refs),
19169
19988
  ...provenance ? { provenance } : {},
19170
19989
  evidence
19171
19990
  });
@@ -19199,7 +20018,7 @@ function parseTopologyContent(raw, dir) {
19199
20018
  }
19200
20019
  seenRels.add(key);
19201
20020
  const prov = r2.provenance;
19202
- relationships.push({ from, to, type, evidence: strList22(r2.evidence ?? prov?.evidence_refs).filter(isRelative) });
20021
+ relationships.push({ from, to, type, evidence: strList32(r2.evidence ?? prov?.evidence_refs).filter(isRelative) });
19203
20022
  }
19204
20023
  return { entities, relationships, findings, ...sourceRevision ? { sourceRevision } : {} };
19205
20024
  }
@@ -19338,7 +20157,7 @@ function parseOriginalSnapshot(fm) {
19338
20157
  function readBody(filePath) {
19339
20158
  try {
19340
20159
  const raw = readFile2(filePath);
19341
- return matter132(raw).content;
20160
+ return matter16(raw).content;
19342
20161
  } catch {
19343
20162
  return "";
19344
20163
  }
@@ -19422,7 +20241,7 @@ function normalizeImpact(ia) {
19422
20241
  ...v.question ? { question: v.question } : {}
19423
20242
  }));
19424
20243
  }
19425
- function optStr32(v) {
20244
+ function optStr42(v) {
19426
20245
  if (typeof v === "string" && v.trim()) return v.trim();
19427
20246
  return void 0;
19428
20247
  }
@@ -19437,13 +20256,13 @@ function parseEvidence(fm) {
19437
20256
  const validations = Array.isArray(d3.validations) ? d3.validations.map((v) => ({
19438
20257
  command: String(v.command ?? ""),
19439
20258
  status: String(v.status ?? "unknown"),
19440
- ...optStr32(v.reason) ? { reason: optStr32(v.reason) } : {}
20259
+ ...optStr42(v.reason) ? { reason: optStr42(v.reason) } : {}
19441
20260
  })) : [];
19442
20261
  const migrations = Array.isArray(d3.migrations) ? d3.migrations.map((m2) => ({
19443
20262
  id: String(m2.id ?? ""),
19444
20263
  environment: String(m2.environment ?? ""),
19445
20264
  status: String(m2.status ?? "unknown"),
19446
- ...optStr32(m2.reason) ? { reason: optStr32(m2.reason) } : {}
20265
+ ...optStr42(m2.reason) ? { reason: optStr42(m2.reason) } : {}
19447
20266
  })) : [];
19448
20267
  out.push({
19449
20268
  module,
@@ -19461,8 +20280,8 @@ function parseReleaseGates(fm) {
19461
20280
  return fm.release_gates.filter((g) => g && typeof g === "object").map((g) => ({
19462
20281
  id: String(g.id ?? ""),
19463
20282
  status: String(g.status ?? "pending"),
19464
- ...optStr32(g.reason) ? { reason: optStr32(g.reason) } : {},
19465
- ...optStr32(g.required_for) ? { requiredFor: optStr32(g.required_for) } : {}
20283
+ ...optStr42(g.reason) ? { reason: optStr42(g.reason) } : {},
20284
+ ...optStr42(g.required_for) ? { requiredFor: optStr42(g.required_for) } : {}
19466
20285
  }));
19467
20286
  }
19468
20287
  function parseExceptions(fm) {
@@ -19470,9 +20289,9 @@ function parseExceptions(fm) {
19470
20289
  return fm.completion_exceptions.filter((e) => e && typeof e === "object").map((e) => ({
19471
20290
  id: String(e.id ?? ""),
19472
20291
  status: String(e.status ?? "proposed"),
19473
- ...optStr32(e.reason) ? { reason: optStr32(e.reason) } : {},
19474
- ...optStr32(e.category) ? { category: optStr32(e.category) } : {},
19475
- ...optStr32(e.impact) ? { impact: optStr32(e.impact) } : {}
20292
+ ...optStr42(e.reason) ? { reason: optStr42(e.reason) } : {},
20293
+ ...optStr42(e.category) ? { category: optStr42(e.category) } : {},
20294
+ ...optStr42(e.impact) ? { impact: optStr42(e.impact) } : {}
19476
20295
  }));
19477
20296
  }
19478
20297
  function parseDecisions(decisions, knowledgeById) {
@@ -19520,7 +20339,7 @@ function normalizeType(raw) {
19520
20339
  if (isValidType(t)) return t;
19521
20340
  return TYPE_ALIASES[t] ?? null;
19522
20341
  }
19523
- var WORK_ITEMS_DIR = "knowledge/delivery/work-items";
20342
+ var WORK_ITEMS_DIR2 = "knowledge/delivery/work-items";
19524
20343
  var WorkItemWriteError = class extends Error {
19525
20344
  constructor(code, message) {
19526
20345
  super(message);
@@ -19532,11 +20351,11 @@ var WorkItemWriteError = class extends Error {
19532
20351
  function revisionOf(raw) {
19533
20352
  return crypto2.createHash("sha256").update(raw, "utf-8").digest("hex");
19534
20353
  }
19535
- function slugify22(s) {
20354
+ function slugify4(s) {
19536
20355
  return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 50);
19537
20356
  }
19538
- function nextWorkItemId(dir) {
19539
- const wiDir = join3(dir, WORK_ITEMS_DIR);
20357
+ function nextWorkItemId2(dir) {
20358
+ const wiDir = join3(dir, WORK_ITEMS_DIR2);
19540
20359
  let max = 0;
19541
20360
  const walk2 = (d3) => {
19542
20361
  if (!exists2(d3)) return;
@@ -19623,7 +20442,7 @@ function ensureH1(body, title) {
19623
20442
  ${trimmed}`;
19624
20443
  }
19625
20444
  function serialize2(data, body) {
19626
- return matter142.stringify(`
20445
+ return matter17.stringify(`
19627
20446
  ${body.trim()}
19628
20447
  `, data);
19629
20448
  }
@@ -19654,7 +20473,7 @@ function createWorkItem(dir, opts) {
19654
20473
  if (!intent) throw new WorkItemWriteError("INVALID_INPUT", "An intent or summary is required.");
19655
20474
  const type = normalizeType(opts.type.trim()) ?? "";
19656
20475
  if (!type) throw new WorkItemWriteError("INVALID_INPUT", `Unknown Work Item type "${opts.type}".`);
19657
- const id = nextWorkItemId(dir);
20476
+ const id = nextWorkItemId2(dir);
19658
20477
  const title = intent.split(/\r?\n/)[0].trim().slice(0, 120);
19659
20478
  const today22 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
19660
20479
  const summary = opts.summary?.trim() || intent;
@@ -19688,7 +20507,7 @@ function createWorkItem(dir, opts) {
19688
20507
  relatedKnowledge: [],
19689
20508
  scopeConfidence: null
19690
20509
  });
19691
- const relPath = `${WORK_ITEMS_DIR}/draft/${id}-${slugify22(title)}.md`;
20510
+ const relPath = `${WORK_ITEMS_DIR2}/draft/${id}-${slugify4(title)}.md`;
19692
20511
  const filePath = join3(dir, relPath);
19693
20512
  if (exists2(filePath)) throw new WorkItemWriteError("INVALID_INPUT", `Work Item file already exists: ${relPath}`);
19694
20513
  const raw = serialize2(data, body);
@@ -20537,7 +21356,7 @@ function detectFormat(raw) {
20537
21356
  if (!trimmed) throw new ImportError("EMPTY_INPUT", "Import content is empty.");
20538
21357
  if (trimmed.startsWith("---")) {
20539
21358
  try {
20540
- const { data } = matter15(trimmed);
21359
+ const { data } = matter18(trimmed);
20541
21360
  if (data && typeof data === "object" && Object.keys(data).length > 0) {
20542
21361
  const d3 = data;
20543
21362
  if (d3.type === "work-item" || d3.id && typeof d3.id === "string" && /^WI-\d+/.test(d3.id)) {
@@ -20559,7 +21378,7 @@ function parseContent(raw) {
20559
21378
  let bodyText = trimmed;
20560
21379
  if (format === "kaddo-frontmatter" || format === "markdown-frontmatter") {
20561
21380
  try {
20562
- const parsed = matter15(trimmed);
21381
+ const parsed = matter18(trimmed);
20563
21382
  frontmatterFields = parsed.data ?? {};
20564
21383
  bodyText = parsed.content.trim();
20565
21384
  } catch {
@@ -20656,7 +21475,7 @@ function findDuplicateByHash(dir, hash) {
20656
21475
  const raw = readFile2(item.filePath);
20657
21476
  if (!raw) continue;
20658
21477
  try {
20659
- const { data } = matter15(raw);
21478
+ const { data } = matter18(raw);
20660
21479
  const source22 = data?.source;
20661
21480
  if (source22 && typeof source22 === "object" && !Array.isArray(source22)) {
20662
21481
  const s = source22;
@@ -21363,377 +22182,82 @@ function getConsentState(config) {
21363
22182
  if (t.consent === "enabled") return "enabled";
21364
22183
  if (t.consent === "disabled") return "disabled";
21365
22184
  if (t.consent === void 0) {
21366
- if (t.enabled === true) return "enabled";
21367
- if (t.enabled === false) return "disabled";
21368
- }
21369
- return "unset";
21370
- }
21371
- function persistConsent(dir, consent) {
21372
- const configPath = join3(dir, ".kaddo", "config.yml");
21373
- if (!exists2(configPath)) return;
21374
- const raw = readFile2(configPath);
21375
- const doc = parseYaml11(raw) ?? {};
21376
- const prev = doc.telemetry ?? {};
21377
- delete prev.enabled;
21378
- doc.telemetry = { ...prev, consent, consentVersion: CURRENT_CONSENT_VERSION };
21379
- writeFile2(configPath, stringifyYaml7(doc));
21380
- }
21381
- function isDeferralActive(dir) {
21382
- const p = join3(dir, ".kaddo", "telemetry-deferred.json");
21383
- if (!exists2(p)) return false;
21384
- try {
21385
- const data = JSON.parse(readFile2(p));
21386
- return data.deferredUntil > Date.now();
21387
- } catch {
21388
- return false;
21389
- }
21390
- }
21391
- function deferConsent(dir) {
21392
- const p = join3(dir, ".kaddo", "telemetry-deferred.json");
21393
- writeFile2(p, JSON.stringify({ deferredUntil: Date.now() + DEFERRAL_DURATION_MS }));
21394
- }
21395
- function consentNotice() {
21396
- return '[Telemetry consent] Anonymous usage telemetry has not been configured.\n Collects: command usage, version, lifecycle events.\n Never collects: source code, knowledge, prompts or PII.\n Use kaddo_set_telemetry_consent to enable, disable, or defer ("not-now").';
21397
- }
21398
- var TELEMETRY_DIR = ".kaddo/telemetry";
21399
- var IDENTITY_FILE = "identity.json";
21400
- function identityPath(dir) {
21401
- return join3(dir, TELEMETRY_DIR, IDENTITY_FILE);
21402
- }
21403
- function loadIdentity(dir) {
21404
- const p = identityPath(dir);
21405
- if (!exists2(p)) return null;
21406
- try {
21407
- return JSON.parse(readFile2(p));
21408
- } catch {
21409
- return null;
21410
- }
21411
- }
21412
- var TELEMETRY_DIR2 = ".kaddo/telemetry";
21413
- var BUFFER_FILE = "buffer.json";
21414
- function bufferPath(dir) {
21415
- return join3(dir, TELEMETRY_DIR2, BUFFER_FILE);
21416
- }
21417
- function readBuffer(dir) {
21418
- const p = bufferPath(dir);
21419
- if (!exists2(p)) return [];
21420
- try {
21421
- const data = JSON.parse(readFile2(p));
21422
- return Array.isArray(data) ? data : [];
21423
- } catch {
21424
- return [];
21425
- }
21426
- }
21427
- function bufferSize(dir) {
21428
- return readBuffer(dir).length;
21429
- }
21430
- function getStatus(dir) {
21431
- const config = loadConfig2(dir);
21432
- const consent = getConsentState(config);
21433
- const enabled = consent === "enabled";
21434
- const identity = loadIdentity(dir);
21435
- return {
21436
- consent,
21437
- enabled,
21438
- registered: identity?.registered ?? false,
21439
- pendingEvents: enabled ? bufferSize(dir) : 0,
21440
- lastError: null
21441
- };
21442
- }
21443
- var WORK_ITEMS_DIR2 = "knowledge/delivery/work-items";
21444
- var DRAFT_DIR = `${WORK_ITEMS_DIR2}/draft`;
21445
- var InitiativeWriteError = class extends Error {
21446
- constructor(message) {
21447
- super(message);
21448
- this.name = "InitiativeWriteError";
21449
- }
21450
- };
21451
- function slugify3(s) {
21452
- return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 50);
21453
- }
21454
- function today2() {
21455
- return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
21456
- }
21457
- function candidateToData(c) {
21458
- const out = { id: c.id, title: c.title };
21459
- if (c.type) out.type = c.type;
21460
- if (c.suggestedKnowledgeLevel) out.suggested_knowledge_level = c.suggestedKnowledgeLevel;
21461
- if (c.expectedValue) out.expected_value = c.expectedValue;
21462
- if (c.notes) out.notes = c.notes;
21463
- if (c.sourceSignals.length) out.source_signals = c.sourceSignals;
21464
- if (c.dependencies.length) out.dependencies = c.dependencies;
21465
- if (c.materializedAs) out.materialized_as = c.materializedAs;
21466
- return out;
21467
- }
21468
- function externalLinkToData(l2) {
21469
- const out = { integration: l2.integration, external_id: l2.externalId };
21470
- if (l2.externalType) out.external_type = l2.externalType;
21471
- if (l2.url) out.url = l2.url;
21472
- if (l2.externalStatus) out.external_status = l2.externalStatus;
21473
- return out;
21474
- }
21475
- function normalizeCandidate(input, fallbackId) {
21476
- return {
21477
- id: input.id?.trim() || fallbackId,
21478
- title: input.title.trim(),
21479
- type: input.type?.trim() || void 0,
21480
- suggestedKnowledgeLevel: input.suggestedKnowledgeLevel?.trim() || void 0,
21481
- expectedValue: input.expectedValue?.trim() || void 0,
21482
- notes: input.notes?.trim() || void 0,
21483
- sourceSignals: input.sourceSignals ?? [],
21484
- dependencies: input.dependencies ?? [],
21485
- materializedAs: input.materializedAs ?? null
21486
- };
21487
- }
21488
- function defaultBody(title) {
21489
- return [
21490
- `# ${title}`,
21491
- "",
21492
- "## Goal",
21493
- "",
21494
- "_What outcome does this Initiative pursue?_",
21495
- "",
21496
- "## Expected Value",
21497
- "",
21498
- "_Why it matters._",
21499
- "",
21500
- "## Scope",
21501
- "",
21502
- "_What this Initiative covers._",
21503
- "",
21504
- "## Out of Scope",
21505
- "",
21506
- "_What it explicitly does not cover._",
21507
- "",
21508
- "## Success Criteria",
21509
- "",
21510
- '_Observable outcomes that define completion (not just "all Work Items done")._',
21511
- "",
21512
- "## Dependencies",
21513
- "",
21514
- "_Other Initiatives, modules, or external work this depends on._",
21515
- "",
21516
- "## Work Item Candidates",
21517
- "",
21518
- "_Candidates live in frontmatter `candidates`; summarize them here as they evolve._",
21519
- "",
21520
- "## Open Questions",
21521
- "",
21522
- "_Unresolved questions for this Initiative._",
21523
- "",
21524
- "## Learning",
21525
- "",
21526
- "_Captured on completion: what was delivered, what stayed out of scope, outcome reached._",
21527
- ""
21528
- ].join("\n");
21529
- }
21530
- function buildInitiativeContent(id, input) {
21531
- const status = input.status ?? "planned";
21532
- if (!isValidInitiativeStatus2(status)) {
21533
- throw new InitiativeWriteError(`Invalid initiative status: ${status}.`);
21534
- }
21535
- const candidates = (input.candidates ?? []).map(
21536
- (c, i) => normalizeCandidate(c, `WI-CANDIDATE-${String(i + 1).padStart(3, "0")}`)
21537
- );
21538
- const data = {
21539
- type: "initiative",
21540
- id,
21541
- title: input.title.trim(),
21542
- status,
21543
- knowledge_level: input.knowledgeLevel ?? "K2",
21544
- domains: input.domains ?? [],
21545
- related_capabilities: input.relatedCapabilities ?? [],
21546
- created_at: today2(),
21547
- external_links: (input.externalLinks ?? []).map(externalLinkToData),
21548
- candidates: candidates.map(candidateToData)
21549
- };
21550
- if (input.horizon) data.horizon = input.horizon;
21551
- if (input.priority) data.priority = input.priority;
21552
- if (input.source) data.source = input.source;
21553
- if (input.sourceId) data.source_id = input.sourceId;
21554
- return matter17.stringify("\n" + (input.body ?? defaultBody(input.title.trim())), data);
21555
- }
21556
- function createInitiative(dir, input) {
21557
- if (!input.title?.trim()) throw new InitiativeWriteError("Initiative title is required.");
21558
- const id = nextInitiativeId(dir);
21559
- const fileName = `${id}-${slugify3(input.title)}.md`;
21560
- const iniDir = join3(dir, INITIATIVES_DIR2);
21561
- ensureDir2(iniDir);
21562
- const filePath = join3(iniDir, fileName);
21563
- const content = buildInitiativeContent(id, input);
21564
- writeFile2(filePath, content);
21565
- const ini = parseInitiative2(dir, filePath, content);
21566
- if (!ini) throw new InitiativeWriteError("Failed to parse the Initiative just written.");
21567
- return ini;
21568
- }
21569
- function transitionInitiative(dir, id, to) {
21570
- const ini = getInitiative(dir, id);
21571
- if (!ini) throw new InitiativeWriteError(`Initiative ${id} not found.`);
21572
- if (!isValidInitiativeStatus2(to)) throw new InitiativeWriteError(`Invalid status: ${to}.`);
21573
- if (ini.status === to) return ini;
21574
- if (!isValidInitiativeTransition(ini.status, to)) {
21575
- throw new InitiativeWriteError(`Invalid transition ${ini.status} \u2192 ${to}.`);
21576
- }
21577
- const raw = readFile2(ini.filePath);
21578
- const { data, content } = matter17(raw, {});
21579
- data.status = to;
21580
- if (to === "completed") data.completed_at = today2();
21581
- const newContent = matter17.stringify(content, data);
21582
- writeFile2(ini.filePath, newContent);
21583
- const updated = parseInitiative2(dir, ini.filePath, newContent);
21584
- if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after transition.");
21585
- return updated;
21586
- }
21587
- function addCandidate(dir, id, candidate) {
21588
- const ini = getInitiative(dir, id);
21589
- if (!ini) throw new InitiativeWriteError(`Initiative ${id} not found.`);
21590
- const raw = readFile2(ini.filePath);
21591
- const { data, content } = matter17(raw, {});
21592
- const existing = Array.isArray(data.candidates) ? data.candidates : [];
21593
- const fallbackId = `WI-CANDIDATE-${String(existing.length + 1).padStart(3, "0")}`;
21594
- const normalized = normalizeCandidate(candidate, fallbackId);
21595
- if (ini.candidates.some((c) => c.id === normalized.id)) {
21596
- throw new InitiativeWriteError(`Candidate ${normalized.id} already exists in ${id}.`);
22185
+ if (t.enabled === true) return "enabled";
22186
+ if (t.enabled === false) return "disabled";
21597
22187
  }
21598
- data.candidates = [...existing, candidateToData(normalized)];
21599
- const newContent = matter17.stringify(content, data);
21600
- writeFile2(ini.filePath, newContent);
21601
- const updated = parseInitiative2(dir, ini.filePath, newContent);
21602
- if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after adding a candidate.");
21603
- return updated;
22188
+ return "unset";
21604
22189
  }
21605
- function updateInitiative(dir, id, patch) {
21606
- const ini = getInitiative(dir, id);
21607
- if (!ini) throw new InitiativeWriteError(`Initiative ${id} not found.`);
21608
- const raw = readFile2(ini.filePath);
21609
- const { data, content } = matter17(raw, {});
21610
- if (patch.title !== void 0) {
21611
- if (!patch.title.trim()) throw new InitiativeWriteError("Initiative title cannot be empty.");
21612
- data.title = patch.title.trim();
21613
- }
21614
- if (patch.horizon !== void 0) data.horizon = patch.horizon;
21615
- if (patch.priority !== void 0) data.priority = patch.priority;
21616
- if (patch.knowledgeLevel !== void 0) data.knowledge_level = patch.knowledgeLevel;
21617
- if (patch.domains !== void 0) data.domains = patch.domains;
21618
- if (patch.relatedCapabilities !== void 0) data.related_capabilities = patch.relatedCapabilities;
21619
- const newContent = matter17.stringify(content, data);
21620
- writeFile2(ini.filePath, newContent);
21621
- const updated = parseInitiative2(dir, ini.filePath, newContent);
21622
- if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after update.");
21623
- return updated;
22190
+ function persistConsent(dir, consent) {
22191
+ const configPath = join3(dir, ".kaddo", "config.yml");
22192
+ if (!exists2(configPath)) return;
22193
+ const raw = readFile2(configPath);
22194
+ const doc = parseYaml11(raw) ?? {};
22195
+ const prev = doc.telemetry ?? {};
22196
+ delete prev.enabled;
22197
+ doc.telemetry = { ...prev, consent, consentVersion: CURRENT_CONSENT_VERSION };
22198
+ writeFile2(configPath, stringifyYaml7(doc));
21624
22199
  }
21625
- function addExternalLink(dir, id, link) {
21626
- const ini = getInitiative(dir, id);
21627
- if (!ini) throw new InitiativeWriteError(`Initiative ${id} not found.`);
21628
- if (!link.integration?.trim() || !link.externalId?.trim()) {
21629
- throw new InitiativeWriteError("External link requires integration and externalId.");
21630
- }
21631
- if (ini.externalLinks.some(
21632
- (l2) => l2.integration.trim().toLowerCase() === link.integration.trim().toLowerCase() && l2.externalId.trim().toLowerCase() === link.externalId.trim().toLowerCase()
21633
- )) {
21634
- throw new InitiativeWriteError(`External link ${link.integration}:${link.externalId} already exists on ${id}.`);
22200
+ function isDeferralActive(dir) {
22201
+ const p = join3(dir, ".kaddo", "telemetry-deferred.json");
22202
+ if (!exists2(p)) return false;
22203
+ try {
22204
+ const data = JSON.parse(readFile2(p));
22205
+ return data.deferredUntil > Date.now();
22206
+ } catch {
22207
+ return false;
21635
22208
  }
21636
- const raw = readFile2(ini.filePath);
21637
- const { data, content } = matter17(raw, {});
21638
- const existing = Array.isArray(data.external_links) ? data.external_links : [];
21639
- data.external_links = [...existing, externalLinkToData(link)];
21640
- const newContent = matter17.stringify(content, data);
21641
- writeFile2(ini.filePath, newContent);
21642
- const updated = parseInitiative2(dir, ini.filePath, newContent);
21643
- if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after adding an external link.");
21644
- return updated;
21645
22209
  }
21646
- function nextWorkItemId2(dir) {
21647
- const wiDir = join3(dir, WORK_ITEMS_DIR2);
21648
- let max = 0;
21649
- const walk2 = (d3) => {
21650
- if (!exists2(d3)) return;
21651
- for (const entry of readDir2(d3)) {
21652
- const full = join3(d3, entry);
21653
- if (isFile2(full)) {
21654
- const m2 = entry.match(/WI-(\d+)/);
21655
- if (m2) max = Math.max(max, parseInt(m2[1], 10));
21656
- } else if (!entry.startsWith(".")) {
21657
- walk2(full);
21658
- }
21659
- }
21660
- };
21661
- walk2(wiDir);
21662
- return `WI-${String(max + 1).padStart(3, "0")}`;
22210
+ function deferConsent(dir) {
22211
+ const p = join3(dir, ".kaddo", "telemetry-deferred.json");
22212
+ writeFile2(p, JSON.stringify({ deferredUntil: Date.now() + DEFERRAL_DURATION_MS }));
21663
22213
  }
21664
- function buildWorkItemContent(wiId, iniId, candidate) {
21665
- const data = {
21666
- type: candidate.type ?? "feature",
21667
- id: wiId,
21668
- title: candidate.title,
21669
- knowledge_level: candidate.suggestedKnowledgeLevel ?? "K2",
21670
- status: "draft",
21671
- phase: "now",
21672
- initiative: iniId,
21673
- domains: [],
21674
- code: [],
21675
- created_at: today2(),
21676
- source: "initiative",
21677
- source_id: candidate.id,
21678
- source_initiative: iniId,
21679
- affected_modules: []
21680
- };
21681
- if (candidate.sourceSignals.length) data.source_signals = candidate.sourceSignals;
21682
- if (candidate.dependencies.length) data.dependencies = candidate.dependencies;
21683
- const body = [
21684
- `# ${candidate.title}`,
21685
- "",
21686
- `> Materialized from Initiative ${iniId}, candidate ${candidate.id}.`,
21687
- "",
21688
- "## Problem",
21689
- "",
21690
- candidate.expectedValue ? `_Expected value:_ ${candidate.expectedValue}` : "_Describe the problem._",
21691
- "",
21692
- "## Acceptance Criteria",
21693
- "",
21694
- "- [ ] _Define at least one testable criterion._",
21695
- "",
21696
- "## Out of scope",
21697
- "",
21698
- "_Not included in this Work Item._",
21699
- "",
21700
- "## Validation",
21701
- "",
21702
- "_How will this be validated?_",
21703
- "",
21704
- "## Learning",
21705
- "",
21706
- "_What did we learn? Update after completion._",
21707
- ""
21708
- ].join("\n");
21709
- return matter17.stringify("\n" + body, data);
22214
+ function consentNotice() {
22215
+ return '[Telemetry consent] Anonymous usage telemetry has not been configured.\n Collects: command usage, version, lifecycle events.\n Never collects: source code, knowledge, prompts or PII.\n Use kaddo_set_telemetry_consent to enable, disable, or defer ("not-now").';
21710
22216
  }
21711
- function materializeCandidate(dir, id, candidateId) {
21712
- const ini = getInitiative(dir, id);
21713
- if (!ini) throw new InitiativeWriteError(`Initiative ${id} not found.`);
21714
- const candidate = ini.candidates.find((c) => c.id === candidateId);
21715
- if (!candidate) throw new InitiativeWriteError(`Candidate ${candidateId} not found in ${id}.`);
21716
- if (candidate.materializedAs) {
21717
- throw new InitiativeWriteError(
21718
- `Candidate ${candidateId} is already materialized as ${candidate.materializedAs}.`
21719
- );
22217
+ var TELEMETRY_DIR = ".kaddo/telemetry";
22218
+ var IDENTITY_FILE = "identity.json";
22219
+ function identityPath(dir) {
22220
+ return join3(dir, TELEMETRY_DIR, IDENTITY_FILE);
22221
+ }
22222
+ function loadIdentity(dir) {
22223
+ const p = identityPath(dir);
22224
+ if (!exists2(p)) return null;
22225
+ try {
22226
+ return JSON.parse(readFile2(p));
22227
+ } catch {
22228
+ return null;
21720
22229
  }
21721
- const wiId = nextWorkItemId2(dir);
21722
- const fileName = `${wiId}-${slugify3(candidate.title)}.md`;
21723
- ensureDir2(join3(dir, DRAFT_DIR));
21724
- writeFile2(join3(dir, DRAFT_DIR, fileName), buildWorkItemContent(wiId, ini.id, candidate));
21725
- const raw = readFile2(ini.filePath);
21726
- const { data, content } = matter17(raw, {});
21727
- const cands = Array.isArray(data.candidates) ? data.candidates : [];
21728
- for (const c of cands) {
21729
- if (c && typeof c === "object" && String(c.id) === candidateId) c.materialized_as = wiId;
22230
+ }
22231
+ var TELEMETRY_DIR2 = ".kaddo/telemetry";
22232
+ var BUFFER_FILE = "buffer.json";
22233
+ function bufferPath(dir) {
22234
+ return join3(dir, TELEMETRY_DIR2, BUFFER_FILE);
22235
+ }
22236
+ function readBuffer(dir) {
22237
+ const p = bufferPath(dir);
22238
+ if (!exists2(p)) return [];
22239
+ try {
22240
+ const data = JSON.parse(readFile2(p));
22241
+ return Array.isArray(data) ? data : [];
22242
+ } catch {
22243
+ return [];
21730
22244
  }
21731
- data.candidates = cands;
21732
- const newContent = matter17.stringify(content, data);
21733
- writeFile2(ini.filePath, newContent);
21734
- const updated = parseInitiative2(dir, ini.filePath, newContent);
21735
- if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after materialization.");
21736
- return { workItemId: wiId, fileName, initiative: updated };
22245
+ }
22246
+ function bufferSize(dir) {
22247
+ return readBuffer(dir).length;
22248
+ }
22249
+ function getStatus(dir) {
22250
+ const config = loadConfig2(dir);
22251
+ const consent = getConsentState(config);
22252
+ const enabled = consent === "enabled";
22253
+ const identity = loadIdentity(dir);
22254
+ return {
22255
+ consent,
22256
+ enabled,
22257
+ registered: identity?.registered ?? false,
22258
+ pendingEvents: enabled ? bufferSize(dir) : 0,
22259
+ lastError: null
22260
+ };
21737
22261
  }
21738
22262
  var SUCCESS_HEADING_RE = /^#{1,6}\s*(success criteria|criterios de[ét]xito|criterios de éxito)\s*$/i;
21739
22263
  var HEADING_RE2 = /^#{1,6}\s+/;
@@ -21843,7 +22367,7 @@ function evaluateInitiativeCompletion(dir, ini) {
21843
22367
  // ../cli/src/core/resource-write.ts
21844
22368
  import fs6 from "fs";
21845
22369
  import path5 from "path";
21846
- import matter23 from "gray-matter";
22370
+ import matter24 from "gray-matter";
21847
22371
  var RESOURCES_DIR = "knowledge/tech/resources";
21848
22372
  var ResourceWriteError = class extends Error {
21849
22373
  constructor(code, message) {
@@ -21953,7 +22477,7 @@ function buildFrontmatter(id, input, base = {}) {
21953
22477
  return data;
21954
22478
  }
21955
22479
  function serialize(data, body) {
21956
- return matter23.stringify(`
22480
+ return matter24.stringify(`
21957
22481
  ${body.trim()}
21958
22482
  `, data);
21959
22483
  }
@@ -21993,7 +22517,7 @@ function updateResource(dir, id, input) {
21993
22517
  throw new ResourceWriteError("VALIDATION_FAILED", findings.filter((f3) => f3.level === "error").map((f3) => f3.message).join("; "));
21994
22518
  }
21995
22519
  const raw = readFile(match.filePath);
21996
- const parsed = matter23(raw);
22520
+ const parsed = matter24(raw);
21997
22521
  const base = parsed.data ?? {};
21998
22522
  const data = buildFrontmatter(match.id || id, input, base);
21999
22523
  let body = parsed.content.trim();
@@ -22226,7 +22750,7 @@ var CANDIDATE_DISCOVERY_GUIDANCE2 = [
22226
22750
  // ../cli/src/core/deprecation-write.ts
22227
22751
  import fs7 from "fs";
22228
22752
  import path6 from "path";
22229
- import matter24 from "gray-matter";
22753
+ import matter25 from "gray-matter";
22230
22754
  var DeprecationWriteError = class extends Error {
22231
22755
  constructor(code, message) {
22232
22756
  super(message);
@@ -22244,14 +22768,14 @@ function atomicWrite2(filePath, content) {
22244
22768
  fs7.renameSync(tmp, filePath);
22245
22769
  }
22246
22770
  function serialize3(data, body) {
22247
- return matter24.stringify(`
22771
+ return matter25.stringify(`
22248
22772
  ${body.trim()}
22249
22773
  `, data);
22250
22774
  }
22251
22775
  function allPlanArtifacts(dir) {
22252
22776
  return discoverKnowledge(dir).filter((a2) => a2.type === DEPRECATION_ARTIFACT_TYPE);
22253
22777
  }
22254
- function nextPlanId(dir, explicit) {
22778
+ function nextPlanId2(dir, explicit) {
22255
22779
  if (explicit && explicit.trim()) return explicit.trim();
22256
22780
  let max = 0;
22257
22781
  for (const a2 of allPlanArtifacts(dir)) {
@@ -22310,7 +22834,7 @@ function validateTargetInput(dir, raw) {
22310
22834
  function loadPlanRaw(dir, planId) {
22311
22835
  const match = allPlanArtifacts(dir).find((a2) => a2.id === planId || a2.title === planId);
22312
22836
  if (!match) throw new DeprecationWriteError("NOT_FOUND", `Deprecation Plan "${planId}" not found.`);
22313
- const parsed = matter24(readFile(match.filePath));
22837
+ const parsed = matter25(readFile(match.filePath));
22314
22838
  const data = { ...parsed.data ?? {} };
22315
22839
  return { filePath: match.filePath, data, body: parsed.content.trim() };
22316
22840
  }
@@ -22327,7 +22851,7 @@ function createPlan(dir, input) {
22327
22851
  if (existing.some((a2) => (a2.title || "").toLowerCase() === title.toLowerCase())) {
22328
22852
  throw new DeprecationWriteError("DUPLICATE", `A plan titled "${title}" already exists.`);
22329
22853
  }
22330
- const id = nextPlanId(dir, input.id);
22854
+ const id = nextPlanId2(dir, input.id);
22331
22855
  const data = {
22332
22856
  type: DEPRECATION_ARTIFACT_TYPE,
22333
22857
  id,
@@ -22498,12 +23022,12 @@ function markWorkItemReady(root, id, confirm2) {
22498
23022
  }
22499
23023
  const raw = readText(root, item.path);
22500
23024
  if (!raw) return fail(`Could not read Work Item file: ${item.path}`);
22501
- const parsed = matter25(raw);
23025
+ const parsed = matter26(raw);
22502
23026
  const data = { ...parsed.data };
22503
23027
  const today3 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
22504
23028
  data.status = "ready";
22505
23029
  data.ready_at = today3;
22506
- const newContent = matter25.stringify(parsed.content, data);
23030
+ const newContent = matter26.stringify(parsed.content, data);
22507
23031
  const posixPath = item.path.replace(/\\/g, "/");
22508
23032
  let newRelPath = item.path;
22509
23033
  if (posixPath.includes("/work-items/draft/")) {
@@ -22798,7 +23322,7 @@ function removeDeprecationTargetTool(root, planId, targetId, confirm2) {
22798
23322
  // src/multirepo.ts
22799
23323
  import fs8 from "fs";
22800
23324
  import path7 from "path";
22801
- import matter26 from "gray-matter";
23325
+ import matter27 from "gray-matter";
22802
23326
  import { parse as parseYaml12, stringify as stringifyYaml8 } from "yaml";
22803
23327
  var ok2 = (data) => ({ ok: true, data });
22804
23328
  var fail2 = (message) => ({ ok: false, message });
@@ -22870,7 +23394,7 @@ function firstParagraph3(md) {
22870
23394
  }
22871
23395
  return "";
22872
23396
  }
22873
- function slugify4(s) {
23397
+ function slugify5(s) {
22874
23398
  return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
22875
23399
  }
22876
23400
  function modulesListTool(root, args) {
@@ -22950,7 +23474,7 @@ function validateWorkItemModulesTool(root, args) {
22950
23474
  if (!wi) return fail2(`Work Item "${args.workItemId}" not found.`);
22951
23475
  const raw = readText(root, wi.path);
22952
23476
  if (!raw) return fail2(`Could not read Work Item file.`);
22953
- const parsed = matter26(raw);
23477
+ const parsed = matter27(raw);
22954
23478
  const data = parsed.data;
22955
23479
  const affectedModules = Array.isArray(data.affected_modules) ? data.affected_modules.map(String) : [];
22956
23480
  const codeEntries = Array.isArray(data.code) ? data.code.filter((c) => typeof c === "object" && c !== null).map((c) => ({ repo: typeof c.repo === "string" ? c.repo : void 0, paths: Array.isArray(c.paths) ? c.paths.map(String) : [] })) : [];
@@ -23003,7 +23527,7 @@ function getWorkItemContextTool(root, args) {
23003
23527
  if (!wi) return fail2(`Work Item "${args.workItemId}" not found.`);
23004
23528
  const raw = readText(root, wi.path);
23005
23529
  if (!raw) return fail2(`Could not read Work Item file.`);
23006
- const parsed = matter26(raw);
23530
+ const parsed = matter27(raw);
23007
23531
  const data = parsed.data;
23008
23532
  const affectedModules = Array.isArray(data.affected_modules) ? data.affected_modules.map(String) : [];
23009
23533
  const codeEntries = Array.isArray(data.code) ? data.code.filter((c) => typeof c === "object" && c !== null).map((c) => ({ repo: typeof c.repo === "string" ? c.repo : void 0, paths: Array.isArray(c.paths) ? c.paths.map(String) : [] })) : [];
@@ -23057,7 +23581,7 @@ function extractSection(content, heading) {
23057
23581
  return out;
23058
23582
  }
23059
23583
  function buildBranchStrategy(wi, affectedModules, codeEntries, descriptor) {
23060
- const slug7 = slugify4(wi.title);
23584
+ const slug7 = slugify5(wi.title);
23061
23585
  const wiLower = wi.id.toLowerCase();
23062
23586
  const coreBranch = `feature/${wiLower}-${slug7}`;
23063
23587
  const moduleBranches = {};
@@ -23089,7 +23613,7 @@ function suggestBranchStrategyTool(root, args) {
23089
23613
  if (!wi) return fail2(`Work Item "${args.workItemId}" not found.`);
23090
23614
  const raw = readText(root, wi.path);
23091
23615
  if (!raw) return fail2(`Could not read Work Item file.`);
23092
- const parsed = matter26(raw);
23616
+ const parsed = matter27(raw);
23093
23617
  const data = parsed.data;
23094
23618
  const affectedModules = Array.isArray(data.affected_modules) ? data.affected_modules.map(String) : [];
23095
23619
  const codeEntries = Array.isArray(data.code) ? data.code.filter((c) => typeof c === "object" && c !== null).map((c) => ({ repo: typeof c.repo === "string" ? c.repo : void 0, paths: Array.isArray(c.paths) ? c.paths.map(String) : [] })) : [];
@@ -23120,7 +23644,7 @@ function exportCapsuleTool(root, args) {
23120
23644
  if (!mc) warnings.push("module-context.md not found for this module.");
23121
23645
  if (mc && isPlaceholder2(mc)) warnings.push("module-context.md contains placeholder sections.");
23122
23646
  capsuleType = "module";
23123
- capsuleName = `${slugify4(config.project?.name ?? "project")}-${args.module}`;
23647
+ capsuleName = `${slugify5(config.project?.name ?? "project")}-${args.module}`;
23124
23648
  capsuleData = {
23125
23649
  type: "knowledge-capsule",
23126
23650
  system: `${config.project?.name ?? "unknown"}/${args.module}`,
@@ -23142,7 +23666,7 @@ function exportCapsuleTool(root, args) {
23142
23666
  if (mc && isPlaceholder2(mc)) warnings.push("module-context.md contains placeholder sections.");
23143
23667
  const modId = config.module?.id ?? args.module;
23144
23668
  capsuleType = "module";
23145
- capsuleName = slugify4(modId);
23669
+ capsuleName = slugify5(modId);
23146
23670
  capsuleData = {
23147
23671
  type: "knowledge-capsule",
23148
23672
  system: `${config.multirepo?.parent_system ?? "unknown"}/${modId}`,
@@ -23170,7 +23694,7 @@ function exportCapsuleTool(root, args) {
23170
23694
  responsibilities.push(mc ? `[${m2.id}] ${firstParagraph3(mc) || m2.name || m2.id}` : `[${m2.id}] ${m2.name || m2.id} (no module-context)`);
23171
23695
  }
23172
23696
  capsuleType = "system";
23173
- capsuleName = `${slugify4(config.project?.name ?? "project")}-system`;
23697
+ capsuleName = `${slugify5(config.project?.name ?? "project")}-system`;
23174
23698
  capsuleData = {
23175
23699
  type: "knowledge-capsule",
23176
23700
  system: config.project?.name ?? "unknown",
@@ -23191,7 +23715,7 @@ function exportCapsuleTool(root, args) {
23191
23715
  if (mc && isPlaceholder2(mc)) warnings.push("module-context.md contains placeholder sections.");
23192
23716
  const modId = config.module?.id ?? "module";
23193
23717
  capsuleType = "module";
23194
- capsuleName = slugify4(modId);
23718
+ capsuleName = slugify5(modId);
23195
23719
  capsuleData = {
23196
23720
  type: "knowledge-capsule",
23197
23721
  system: `${config.multirepo?.parent_system ?? "unknown"}/${modId}`,
@@ -23207,7 +23731,7 @@ function exportCapsuleTool(root, args) {
23207
23731
  };
23208
23732
  } else {
23209
23733
  capsuleType = "project";
23210
- capsuleName = slugify4(config.project?.name ?? "project");
23734
+ capsuleName = slugify5(config.project?.name ?? "project");
23211
23735
  capsuleData = {
23212
23736
  type: "knowledge-capsule",
23213
23737
  system: config.project?.name ?? "unknown",
@@ -23370,7 +23894,7 @@ var PROVENANCE2 = /* @__PURE__ */ new Set(["declared", "derived", "agent-reviewe
23370
23894
  function isRelative2(p) {
23371
23895
  return typeof p === "string" && p.trim() !== "" && !/^([a-zA-Z]:[\\/]|\/)/.test(p) && !p.includes("..");
23372
23896
  }
23373
- function strList3(v) {
23897
+ function strList4(v) {
23374
23898
  return Array.isArray(v) ? v.map((x2) => typeof x2 === "string" ? x2.trim() : "").filter(Boolean) : [];
23375
23899
  }
23376
23900
  function loadSystemTopology2(dir) {
@@ -23411,7 +23935,7 @@ function parseTopologyContent2(raw, dir) {
23411
23935
  const moduleRaw = typeof e.module === "string" ? e.module.trim() : void 0;
23412
23936
  if (moduleRaw && !validModules.has(moduleRaw)) findings.push({ level: "warning", message: `Entity "${id}" references unregistered module "${moduleRaw}".` });
23413
23937
  const rawImpl = e.implementation ?? e.implementation_refs;
23414
- const implementation = strList3(rawImpl).filter((p) => {
23938
+ const implementation = strList4(rawImpl).filter((p) => {
23415
23939
  if (isRelative2(p)) return true;
23416
23940
  findings.push({ level: "warning", message: `Entity "${id}" implementation ref "${p}" is not a safe relative path and was dropped.` });
23417
23941
  return false;
@@ -23419,7 +23943,7 @@ function parseTopologyContent2(raw, dir) {
23419
23943
  const prov = e.provenance;
23420
23944
  const provOrigin = typeof prov === "string" ? prov : prov && typeof prov === "object" && typeof prov.origin === "string" ? String(prov.origin) : void 0;
23421
23945
  const provenance = provOrigin && PROVENANCE2.has(provOrigin) ? provOrigin : void 0;
23422
- const evidence = strList3(e.evidence ?? (prov && typeof prov === "object" ? prov.evidence_refs : void 0)).filter(isRelative2);
23946
+ const evidence = strList4(e.evidence ?? (prov && typeof prov === "object" ? prov.evidence_refs : void 0)).filter(isRelative2);
23423
23947
  seenIds.add(id);
23424
23948
  entities.push({
23425
23949
  id,
@@ -23428,7 +23952,7 @@ function parseTopologyContent2(raw, dir) {
23428
23952
  ...typeof e.purpose === "string" && e.purpose.trim() ? { purpose: e.purpose.trim() } : {},
23429
23953
  ...moduleRaw && validModules.has(moduleRaw) ? { moduleId: moduleRaw } : {},
23430
23954
  implementationRefs: implementation,
23431
- knowledgeRefs: strList3(e.knowledge ?? e.knowledge_refs),
23955
+ knowledgeRefs: strList4(e.knowledge ?? e.knowledge_refs),
23432
23956
  ...provenance ? { provenance } : {},
23433
23957
  evidence
23434
23958
  });
@@ -23462,7 +23986,7 @@ function parseTopologyContent2(raw, dir) {
23462
23986
  }
23463
23987
  seenRels.add(key);
23464
23988
  const prov = r2.provenance;
23465
- relationships.push({ from, to, type, evidence: strList3(r2.evidence ?? prov?.evidence_refs).filter(isRelative2) });
23989
+ relationships.push({ from, to, type, evidence: strList4(r2.evidence ?? prov?.evidence_refs).filter(isRelative2) });
23466
23990
  }
23467
23991
  return { entities, relationships, findings, ...sourceRevision ? { sourceRevision } : {} };
23468
23992
  }
@@ -23849,14 +24373,14 @@ function systemImpactCandidatesTool(root, args) {
23849
24373
  }
23850
24374
 
23851
24375
  // ../cli/src/services/integrations.ts
23852
- import matter28 from "gray-matter";
24376
+ import matter29 from "gray-matter";
23853
24377
  import { parse as parseYaml14, stringify as stringifyYaml10 } from "yaml";
23854
24378
 
23855
24379
  // ../cli/src/core/work-item-write.ts
23856
24380
  import fs10 from "fs";
23857
24381
  import path9 from "path";
23858
24382
  import crypto4 from "crypto";
23859
- import matter27 from "gray-matter";
24383
+ import matter28 from "gray-matter";
23860
24384
  var SECTION_ORDER2 = [
23861
24385
  "Actor",
23862
24386
  "Outcome",
@@ -24904,7 +25428,7 @@ async function integrationsWorkItemTool(root, args) {
24904
25428
  }
24905
25429
 
24906
25430
  // ../cli/src/core/context-pack.ts
24907
- import matter29 from "gray-matter";
25431
+ import matter30 from "gray-matter";
24908
25432
  var CONTEXT_PACK_VERSION = "1";
24909
25433
  var ARCH_DIR4 = "knowledge";
24910
25434
  function readScanJson(dir) {
@@ -24917,7 +25441,7 @@ function readScanJson(dir) {
24917
25441
  }
24918
25442
  }
24919
25443
  function firstParagraph4(markdown) {
24920
- const body = matter29(markdown).content.trim();
25444
+ const body = matter30(markdown).content.trim();
24921
25445
  const para = body.split("\n\n").map((p) => p.trim()).find((p) => p && !p.startsWith("#"));
24922
25446
  return para ?? "";
24923
25447
  }
@@ -25900,7 +26424,7 @@ function renderUnderstand(plan) {
25900
26424
 
25901
26425
  // ../cli/src/core/knowledge-consolidation.ts
25902
26426
  import { createHash as createHash3 } from "crypto";
25903
- import matter30 from "gray-matter";
26427
+ import matter31 from "gray-matter";
25904
26428
  var KNOWLEDGE7 = "knowledge";
25905
26429
  var CONSOLIDATION_CATEGORIES = [
25906
26430
  "knowledge_redundancy",
@@ -25910,12 +26434,12 @@ var CONSOLIDATION_CATEGORIES = [
25910
26434
  "potentially_outdated",
25911
26435
  "knowledge_organization"
25912
26436
  ];
25913
- function toPosix8(p) {
26437
+ function toPosix9(p) {
25914
26438
  return p.replace(/\\/g, "/");
25915
26439
  }
25916
26440
  function relFromDir(dir, filePath) {
25917
- const d3 = toPosix8(dir).replace(/\/+$/, "");
25918
- const f3 = toPosix8(filePath);
26441
+ const d3 = toPosix9(dir).replace(/\/+$/, "");
26442
+ const f3 = toPosix9(filePath);
25919
26443
  return f3.startsWith(d3 + "/") ? f3.slice(d3.length + 1) : f3;
25920
26444
  }
25921
26445
  function sha256(content) {
@@ -26033,7 +26557,7 @@ function stableOpportunityId(category, sources) {
26033
26557
  function layerOf(a2) {
26034
26558
  const byType = layerForType(a2.type);
26035
26559
  if (byType) return byType;
26036
- const p = toPosix8(a2.filePath);
26560
+ const p = toPosix9(a2.filePath);
26037
26561
  if (p.includes(`/${KNOWLEDGE7}/business/`)) return "Business";
26038
26562
  if (p.includes(`/${KNOWLEDGE7}/product/`)) return "Product";
26039
26563
  if (p.includes(`/${KNOWLEDGE7}/tech/`)) return "Tech";
@@ -26057,7 +26581,7 @@ function buildConsolidationPlan(dir, opts = {}) {
26057
26581
  const loaded = rawArtifacts.map((a2) => {
26058
26582
  let body = "";
26059
26583
  try {
26060
- body = matter30(readFile(a2.filePath)).content;
26584
+ body = matter31(readFile(a2.filePath)).content;
26061
26585
  } catch {
26062
26586
  body = "";
26063
26587
  }
@@ -26311,14 +26835,14 @@ function describeScope(scope) {
26311
26835
  return `module:${scope.module}`;
26312
26836
  }
26313
26837
  function isWorkItem(la) {
26314
- return toPosix8(la.artifact.filePath).includes("/delivery/work-items/");
26838
+ return toPosix9(la.artifact.filePath).includes("/delivery/work-items/");
26315
26839
  }
26316
26840
  function isCompletedWorkItem(la) {
26317
- const p = toPosix8(la.artifact.filePath);
26841
+ const p = toPosix9(la.artifact.filePath);
26318
26842
  return p.includes("/delivery/work-items/completed/") || isWorkItem(la) && la.artifact.status === "completed";
26319
26843
  }
26320
26844
  function isScaffolding(la) {
26321
- const p = toPosix8(la.artifact.filePath);
26845
+ const p = toPosix9(la.artifact.filePath);
26322
26846
  return p.includes(`/${KNOWLEDGE7}/agents/`) || p.includes(`/${KNOWLEDGE7}/skills/`) || p.includes(`/${KNOWLEDGE7}/external/`) || la.artifact.type === "knowledge-capsule";
26323
26847
  }
26324
26848
  var NON_KNOWLEDGE_DOC_TYPES = /* @__PURE__ */ new Set([
@@ -26386,7 +26910,7 @@ function decisionFileExists(dir, decisionId) {
26386
26910
  function hintInScope(hint, scoped) {
26387
26911
  if (scoped.length === 0) return false;
26388
26912
  return scoped.some(
26389
- (la) => la.artifact.id === hint.artifact_id || hint.path && toPosix8(la.artifact.filePath).endsWith(toPosix8(hint.path))
26913
+ (la) => la.artifact.id === hint.artifact_id || hint.path && toPosix9(la.artifact.filePath).endsWith(toPosix9(hint.path))
26390
26914
  );
26391
26915
  }
26392
26916
  function relationshipOpportunityFromHint(hint) {
@@ -26770,6 +27294,104 @@ function completeInitiativeTool(root, args) {
26770
27294
  }
26771
27295
  }
26772
27296
 
27297
+ // src/planning.ts
27298
+ var ok6 = (data) => ({ ok: true, data });
27299
+ var fail5 = (message) => ({ ok: false, message });
27300
+ function previewOrApply2(confirm2, action, effect, apply) {
27301
+ if (!confirm2) return ok6({ action, effect, instruction: "Call again with confirm=true to apply." });
27302
+ try {
27303
+ return ok6({ applied: true, result: apply() });
27304
+ } catch (err) {
27305
+ if (err instanceof DeliveryPlanningWriteError) return fail5(err.message);
27306
+ throw err;
27307
+ }
27308
+ }
27309
+ function listPlansTool(root) {
27310
+ return ok6(discoverDeliveryPlans2(root).map((p) => {
27311
+ const prog = computePlanProgress(root, p);
27312
+ return { id: p.id, title: p.title, status: p.status, milestones: p.milestones.length, epics: prog.epics.total, delivery: prog.delivery };
27313
+ }));
27314
+ }
27315
+ function getPlanTool(root, id) {
27316
+ const plan = getPlan(root, id);
27317
+ if (!plan) return fail5(`Plan ${id} not found.`);
27318
+ return ok6({
27319
+ id: plan.id,
27320
+ title: plan.title,
27321
+ status: plan.status,
27322
+ objective: plan.objective,
27323
+ milestones: plan.milestones,
27324
+ dependsOn: plan.dependsOn,
27325
+ successCriteria: plan.successCriteria,
27326
+ risks: plan.risks,
27327
+ references: plan.references,
27328
+ epics: epicsInPlan(root, plan.id).map((e) => ({ id: e.id, title: e.title, status: e.status, milestone: e.milestone }))
27329
+ });
27330
+ }
27331
+ function getPlanProgressTool(root, id) {
27332
+ const plan = getPlan(root, id);
27333
+ if (!plan) return fail5(`Plan ${id} not found.`);
27334
+ return ok6(computePlanProgress(root, plan));
27335
+ }
27336
+ function listEpicsTool(root) {
27337
+ return ok6(discoverEpics2(root).map((e) => {
27338
+ const prog = computeEpicProgress(root, e);
27339
+ return { id: e.id, title: e.title, status: e.status, plan: e.plan, milestone: e.milestone, delivery: prog.delivery };
27340
+ }));
27341
+ }
27342
+ function getEpicTool(root, id) {
27343
+ const epic = getEpic(root, id);
27344
+ if (!epic) return fail5(`Epic ${id} not found.`);
27345
+ return ok6({
27346
+ id: epic.id,
27347
+ title: epic.title,
27348
+ status: epic.status,
27349
+ outcome: epic.outcome,
27350
+ plan: epic.plan,
27351
+ milestone: epic.milestone,
27352
+ dependsOn: epic.dependsOn,
27353
+ successCriteria: epic.successCriteria,
27354
+ relatedCapabilities: epic.relatedCapabilities,
27355
+ initiatives: initiativesInEpic(root, epic.id).map((i) => ({ id: i.id, title: i.title, status: i.status, owners: i.owners, contributors: i.contributors })),
27356
+ workItems: workItemsInEpic(root, epic.id).map((w3) => ({ id: w3.id, title: w3.title, status: w3.lifecycle }))
27357
+ });
27358
+ }
27359
+ function getEpicProgressTool(root, id) {
27360
+ const epic = getEpic(root, id);
27361
+ if (!epic) return fail5(`Epic ${id} not found.`);
27362
+ return ok6(computeEpicProgress(root, epic));
27363
+ }
27364
+ function validatePlanningTool(root) {
27365
+ return ok6({ findings: validatePlanningGraph(root) });
27366
+ }
27367
+ function createPlanTool(root, args) {
27368
+ if (!args.title?.trim()) return fail5("title is required.");
27369
+ return previewOrApply2(
27370
+ args.confirm,
27371
+ "create-delivery-plan",
27372
+ `A new Delivery Plan "${args.title.trim()}" will be created under knowledge/delivery/plans/.`,
27373
+ () => createDeliveryPlan(root, { title: args.title, objective: args.objective })
27374
+ );
27375
+ }
27376
+ function createEpicTool(root, args) {
27377
+ if (!args.title?.trim()) return fail5("title is required.");
27378
+ return previewOrApply2(
27379
+ args.confirm,
27380
+ "create-epic",
27381
+ `A new Epic "${args.title.trim()}"${args.plan ? ` in ${args.plan}` : ""} will be created under knowledge/delivery/epics/.`,
27382
+ () => createEpic(root, { title: args.title, plan: args.plan, milestone: args.milestone, outcome: args.outcome })
27383
+ );
27384
+ }
27385
+ function associateInitiativeTool(root, args) {
27386
+ if (!args.epic?.trim() || !args.initiative?.trim()) return fail5("epic and initiative are required.");
27387
+ return previewOrApply2(
27388
+ args.confirm,
27389
+ "associate-initiative-epic",
27390
+ `Initiative ${args.initiative} will be associated with Epic ${args.epic}.`,
27391
+ () => associateInitiativeWithEpic(root, args.initiative, args.epic)
27392
+ );
27393
+ }
27394
+
26773
27395
  // src/server.ts
26774
27396
  var SERVER_NAME = "kaddo";
26775
27397
  var require2 = createRequire(import.meta.url);
@@ -27334,6 +27956,58 @@ function createServer(root) {
27334
27956
  { title: "Get initiative progress", description: `Planning coverage (candidates materialized/total) and delivery progress (Work Items by lifecycle state). ${INITIATIVE_READ_NOTE}`, inputSchema: { id: z4.string() } },
27335
27957
  async (args) => toolText(guarded(root, () => getInitiativeProgressTool(root, args.id)))
27336
27958
  );
27959
+ const PLANNING_READ_NOTE = "Read-only. Plans and Epics are an OPTIONAL planning layer; progress is derived from real Work Item state and never auto-completes.";
27960
+ const PLANNING_WRITE_NOTE = "Without confirm=true returns a preview. With confirm=true applies the change. Writes only under knowledge/delivery/; never changes another element lifecycle, never infers responsibility from Git.";
27961
+ server.registerTool(
27962
+ "kaddo_list_plans",
27963
+ { title: "List delivery plans", description: `List delivery plans with milestone count, epics and delivery progress. ${PLANNING_READ_NOTE}`, inputSchema: {} },
27964
+ async () => toolText(guarded(root, () => listPlansTool(root)))
27965
+ );
27966
+ server.registerTool(
27967
+ "kaddo_get_plan",
27968
+ { title: "Get delivery plan", description: `Get one plan: objective, milestones, dependencies, success criteria and its epics. ${PLANNING_READ_NOTE}`, inputSchema: { id: z4.string() } },
27969
+ async (args) => toolText(guarded(root, () => getPlanTool(root, args.id)))
27970
+ );
27971
+ server.registerTool(
27972
+ "kaddo_get_plan_progress",
27973
+ { title: "Get plan progress", description: `Planning coverage, delivery progress and completion readiness for a plan. ${PLANNING_READ_NOTE}`, inputSchema: { id: z4.string() } },
27974
+ async (args) => toolText(guarded(root, () => getPlanProgressTool(root, args.id)))
27975
+ );
27976
+ server.registerTool(
27977
+ "kaddo_list_epics",
27978
+ { title: "List epics", description: `List epics with their plan/milestone and delivery progress. ${PLANNING_READ_NOTE}`, inputSchema: {} },
27979
+ async () => toolText(guarded(root, () => listEpicsTool(root)))
27980
+ );
27981
+ server.registerTool(
27982
+ "kaddo_get_epic",
27983
+ { title: "Get epic", description: `Get one epic: outcome, plan/milestone, dependencies, initiatives and work items (derived). ${PLANNING_READ_NOTE}`, inputSchema: { id: z4.string() } },
27984
+ async (args) => toolText(guarded(root, () => getEpicTool(root, args.id)))
27985
+ );
27986
+ server.registerTool(
27987
+ "kaddo_get_epic_progress",
27988
+ { title: "Get epic progress", description: `Planning coverage, delivery progress and completion readiness for an epic. ${PLANNING_READ_NOTE}`, inputSchema: { id: z4.string() } },
27989
+ async (args) => toolText(guarded(root, () => getEpicProgressTool(root, args.id)))
27990
+ );
27991
+ server.registerTool(
27992
+ "kaddo_validate_planning",
27993
+ { title: "Validate planning graph", description: `Deterministic validation: invalid references, dependency cycles, duplicate milestones and pending dependencies. ${PLANNING_READ_NOTE}`, inputSchema: {} },
27994
+ async () => toolText(guarded(root, () => validatePlanningTool(root)))
27995
+ );
27996
+ server.registerTool(
27997
+ "kaddo_create_plan",
27998
+ { title: "Create delivery plan", description: `Create a new delivery plan. ${PLANNING_WRITE_NOTE}`, inputSchema: { title: z4.string(), objective: z4.string().optional(), confirm: z4.boolean().optional() } },
27999
+ async (args) => toolText(guarded(root, () => createPlanTool(root, args)))
28000
+ );
28001
+ server.registerTool(
28002
+ "kaddo_create_epic",
28003
+ { title: "Create epic", description: `Create a new epic, optionally in a plan/milestone (validated). ${PLANNING_WRITE_NOTE}`, inputSchema: { title: z4.string(), plan: z4.string().optional(), milestone: z4.string().optional(), outcome: z4.string().optional(), confirm: z4.boolean().optional() } },
28004
+ async (args) => toolText(guarded(root, () => createEpicTool(root, args)))
28005
+ );
28006
+ server.registerTool(
28007
+ "kaddo_associate_initiative_epic",
28008
+ { title: "Associate initiative with epic", description: `Associate an existing Initiative with an Epic (validated). ${PLANNING_WRITE_NOTE}`, inputSchema: { epic: z4.string(), initiative: z4.string(), confirm: z4.boolean().optional() } },
28009
+ async (args) => toolText(guarded(root, () => associateInitiativeTool(root, args)))
28010
+ );
27337
28011
  const INITIATIVE_WRITE_NOTE = "Without confirm=true returns a preview. With confirm=true applies the change. Writes only under knowledge/delivery/.";
27338
28012
  server.registerTool(
27339
28013
  "kaddo_create_initiative",