@kaddo/mcp 3.134.0 → 3.135.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 +683 -130
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2903,7 +2903,7 @@ ${items.map((i2) => `- ${i2}`).join("\n")}
2903
2903
  `;
2904
2904
  }
2905
2905
  function renderCapsuleMarkdown(c3) {
2906
- const fm = [
2906
+ const fm2 = [
2907
2907
  "---",
2908
2908
  "type: knowledge-capsule",
2909
2909
  `system: ${c3.system}`,
@@ -2915,7 +2915,7 @@ function renderCapsuleMarkdown(c3) {
2915
2915
  "---"
2916
2916
  ].join("\n");
2917
2917
  const parts = [
2918
- fm,
2918
+ fm2,
2919
2919
  "",
2920
2920
  `# ${c3.system} \u2014 Knowledge Capsule`,
2921
2921
  "",
@@ -3365,40 +3365,40 @@ function purposeFromBody(body) {
3365
3365
  }
3366
3366
  function toDetail(filePath, relPath, raw) {
3367
3367
  const parsed = matter7(raw);
3368
- const fm = parsed.data ?? {};
3368
+ const fm2 = parsed.data ?? {};
3369
3369
  const body = parsed.content.trim();
3370
- const id = str(fm.id) ?? "";
3370
+ const id = str(fm2.id) ?? "";
3371
3371
  return {
3372
3372
  id,
3373
- title: str(fm.title) ?? id,
3374
- resourceType: str(fm.resource_type),
3375
- provider: str(fm.provider),
3376
- environments: strArray(fm.environments),
3377
- scope: parseScope(fm.scope),
3378
- modules: strArray(fm.modules),
3373
+ title: str(fm2.title) ?? id,
3374
+ resourceType: str(fm2.resource_type),
3375
+ provider: str(fm2.provider),
3376
+ environments: strArray(fm2.environments),
3377
+ scope: parseScope(fm2.scope),
3378
+ modules: strArray(fm2.modules),
3379
3379
  path: relPath,
3380
- purpose: str(fm.purpose) ?? purposeFromBody(body),
3381
- interfaces: parseInterfaces(fm.access_interfaces),
3382
- boundaries: parseBoundaries(fm.access_boundaries),
3380
+ purpose: str(fm2.purpose) ?? purposeFromBody(body),
3381
+ interfaces: parseInterfaces(fm2.access_interfaces),
3382
+ boundaries: parseBoundaries(fm2.access_boundaries),
3383
3383
  ...(() => {
3384
- const a3 = parseAuth(fm.authentication);
3384
+ const a3 = parseAuth(fm2.authentication);
3385
3385
  return { authRefs: a3.refs, authMode: a3.mode };
3386
3386
  })(),
3387
- connectionProfiles: parseConnectionProfiles(fm.connection_profiles),
3387
+ connectionProfiles: parseConnectionProfiles(fm2.connection_profiles),
3388
3388
  markdownBody: body
3389
3389
  };
3390
3390
  }
3391
3391
  function getResources(dir) {
3392
3392
  return discoverKnowledge(dir).filter((a3) => a3.type === RESOURCE_ARTIFACT_TYPE).map((a3) => {
3393
- const fm = a3.rawFrontmatter;
3393
+ const fm2 = a3.rawFrontmatter;
3394
3394
  return {
3395
- id: a3.id || str(fm.id) || a3.title,
3396
- title: a3.title || str(fm.title) || a3.id,
3397
- resourceType: str(fm.resource_type),
3398
- provider: str(fm.provider),
3399
- environments: strArray(fm.environments),
3400
- scope: parseScope(fm.scope),
3401
- modules: strArray(fm.modules),
3395
+ id: a3.id || str(fm2.id) || a3.title,
3396
+ title: a3.title || str(fm2.title) || a3.id,
3397
+ resourceType: str(fm2.resource_type),
3398
+ provider: str(fm2.provider),
3399
+ environments: strArray(fm2.environments),
3400
+ scope: parseScope(fm2.scope),
3401
+ modules: strArray(fm2.modules),
3402
3402
  path: a3.relPath
3403
3403
  };
3404
3404
  }).sort((x2, y) => x2.id.localeCompare(y.id));
@@ -3411,8 +3411,8 @@ function getResource(dir, id) {
3411
3411
  const raw = readFile(match.filePath);
3412
3412
  return toDetail(match.filePath, match.relPath, raw);
3413
3413
  }
3414
- function parseWorkItemResources(fm) {
3415
- const v = fm.resources;
3414
+ function parseWorkItemResources(fm2) {
3415
+ const v = fm2.resources;
3416
3416
  if (!Array.isArray(v)) return [];
3417
3417
  const out = [];
3418
3418
  for (const raw of v) {
@@ -3464,8 +3464,8 @@ var EVIDENCE_SIGNALS = [
3464
3464
  function str2(v) {
3465
3465
  return typeof v === "string" && v.trim() ? v.trim() : null;
3466
3466
  }
3467
- function parseWorkItemDeprecationTargets(fm) {
3468
- const v = fm.deprecation_targets;
3467
+ function parseWorkItemDeprecationTargets(fm2) {
3468
+ const v = fm2.deprecation_targets;
3469
3469
  if (!Array.isArray(v)) return [];
3470
3470
  const out = [];
3471
3471
  for (const raw of v) {
@@ -3599,13 +3599,13 @@ function purposeFromBody2(body) {
3599
3599
  }
3600
3600
  function getDeprecationPlans(dir) {
3601
3601
  return discoverKnowledge(dir).filter((a3) => a3.type === DEPRECATION_ARTIFACT_TYPE).map((a3) => {
3602
- const fm = a3.rawFrontmatter;
3603
- const targets = parseTargets(fm.targets);
3602
+ const fm2 = a3.rawFrontmatter;
3603
+ const targets = parseTargets(fm2.targets);
3604
3604
  return {
3605
- id: a3.id || str2(fm.id) || a3.title,
3606
- title: a3.title || str2(fm.title) || a3.id,
3605
+ id: a3.id || str2(fm2.id) || a3.title,
3606
+ title: a3.title || str2(fm2.title) || a3.id,
3607
3607
  path: a3.relPath,
3608
- status: str2(fm.status),
3608
+ status: str2(fm2.status),
3609
3609
  targetCount: targets.length,
3610
3610
  lifecycleCounts: lifecycleCountsOf(targets)
3611
3611
  };
@@ -3618,17 +3618,17 @@ function getDeprecationPlan(dir, id) {
3618
3618
  if (!match) return null;
3619
3619
  const raw = readFile(match.filePath);
3620
3620
  const parsed = matter8(raw);
3621
- const fm = parsed.data ?? {};
3621
+ const fm2 = parsed.data ?? {};
3622
3622
  const body = parsed.content.trim();
3623
- const targets = parseTargets(fm.targets);
3623
+ const targets = parseTargets(fm2.targets);
3624
3624
  return {
3625
- id: str2(fm.id) ?? match.id,
3626
- title: str2(fm.title) ?? match.title,
3625
+ id: str2(fm2.id) ?? match.id,
3626
+ title: str2(fm2.title) ?? match.title,
3627
3627
  path: match.relPath,
3628
- status: str2(fm.status),
3628
+ status: str2(fm2.status),
3629
3629
  targetCount: targets.length,
3630
3630
  lifecycleCounts: lifecycleCountsOf(targets),
3631
- purpose: str2(fm.purpose) ?? purposeFromBody2(body),
3631
+ purpose: str2(fm2.purpose) ?? purposeFromBody2(body),
3632
3632
  targets,
3633
3633
  markdownBody: body
3634
3634
  };
@@ -4154,9 +4154,9 @@ var QUALITY_NOTE = {
4154
4154
  sparse: "The graph has many nodes but few meaningful edges.",
4155
4155
  empty: "The graph has almost no relationships."
4156
4156
  };
4157
- function yamlBlock(fm) {
4157
+ function yamlBlock(fm2) {
4158
4158
  const lines = ["```yaml"];
4159
- for (const [k2, vals] of Object.entries(fm)) {
4159
+ for (const [k2, vals] of Object.entries(fm2)) {
4160
4160
  lines.push(`${k2}:`);
4161
4161
  for (const v of vals) lines.push(` - ${v}`);
4162
4162
  }
@@ -9348,7 +9348,7 @@ var AGENT_PROMPTS = [
9348
9348
  // ../cli/src/modules/agents.ts
9349
9349
  function withAgentFrontMatter(fileName, content) {
9350
9350
  const name = fileName.replace(/\.md$/, "");
9351
- const fm = [
9351
+ const fm2 = [
9352
9352
  "---",
9353
9353
  "type: agent",
9354
9354
  `name: ${name}`,
@@ -9357,7 +9357,7 @@ function withAgentFrontMatter(fileName, content) {
9357
9357
  "---",
9358
9358
  ""
9359
9359
  ].join("\n");
9360
- return fm + content.replace(/^\s+/, "");
9360
+ return fm2 + content.replace(/^\s+/, "");
9361
9361
  }
9362
9362
  var agentReadme = {
9363
9363
  path: "knowledge/agents/README.md",
@@ -10385,8 +10385,8 @@ function analyzeMetadataHealth(dir) {
10385
10385
  if (isFile(p) && entry.endsWith(".md")) {
10386
10386
  try {
10387
10387
  const raw = readFile(p);
10388
- const fm = matter12(raw).data;
10389
- if (fm.status === "done") {
10388
+ const fm2 = matter12(raw).data;
10389
+ if (fm2.status === "done") {
10390
10390
  const rel = p.replace(dir + "/", "").replace(dir + "\\", "").replace(/\\/g, "/");
10391
10391
  findings.push({
10392
10392
  file: rel,
@@ -10719,8 +10719,8 @@ function buildProjectExplanation(dir) {
10719
10719
  };
10720
10720
  }),
10721
10721
  implementationEvidence: workItemArtifacts.filter((a3) => a3.affectedModules.length > 0).map((a3) => {
10722
- const fm = a3.rawFrontmatter;
10723
- const evidence = fm.implementation_evidence;
10722
+ const fm2 = a3.rawFrontmatter;
10723
+ const evidence = fm2.implementation_evidence;
10724
10724
  const repos = evidence?.repositories ?? {};
10725
10725
  const repoEvidence = {};
10726
10726
  for (const [repoId, data] of Object.entries(repos)) {
@@ -10738,8 +10738,8 @@ function buildProjectExplanation(dir) {
10738
10738
  validationStatus: a3.validationStatus || historicalDefault,
10739
10739
  releaseStatus: a3.releaseStatus || "not-assessed",
10740
10740
  affectedModules: a3.affectedModules,
10741
- releaseGates: Array.isArray(fm.release_gates) ? fm.release_gates : [],
10742
- completionExceptions: Array.isArray(fm.completion_exceptions) ? fm.completion_exceptions : [],
10741
+ releaseGates: Array.isArray(fm2.release_gates) ? fm2.release_gates : [],
10742
+ completionExceptions: Array.isArray(fm2.completion_exceptions) ? fm2.completion_exceptions : [],
10743
10743
  repoEvidence
10744
10744
  };
10745
10745
  })
@@ -13025,6 +13025,7 @@ import { parse as parseYaml32 } from "yaml";
13025
13025
  import { parse as parseYaml52, stringify as stringifyYaml32 } from "yaml";
13026
13026
  import { parse as parseYaml42, stringify as stringifyYaml22 } from "yaml";
13027
13027
  import matter32 from "gray-matter";
13028
+ import { createHash as createHash3 } from "crypto";
13028
13029
  import matter42 from "gray-matter";
13029
13030
  import matter52 from "gray-matter";
13030
13031
  import matter62 from "gray-matter";
@@ -21579,6 +21580,18 @@ var DELIVERY_PLANS_DIR2 = "knowledge/delivery/plans";
21579
21580
  var EPICS_DIR2 = "knowledge/delivery/epics";
21580
21581
  var PLAN_STATES2 = ["draft", "in-progress", "completed", "cancelled"];
21581
21582
  var EPIC_STATES2 = ["planned", "in-progress", "completed", "cancelled"];
21583
+ var PLAN_TRANSITIONS = {
21584
+ draft: ["in-progress", "cancelled"],
21585
+ "in-progress": ["completed", "cancelled"],
21586
+ completed: [],
21587
+ cancelled: []
21588
+ };
21589
+ var EPIC_TRANSITIONS = {
21590
+ planned: ["in-progress", "cancelled"],
21591
+ "in-progress": ["completed", "cancelled"],
21592
+ completed: [],
21593
+ cancelled: []
21594
+ };
21582
21595
  function toPosix32(p) {
21583
21596
  return p.replace(/\\/g, "/");
21584
21597
  }
@@ -21859,6 +21872,9 @@ function validatePlanningGraph(dir) {
21859
21872
  findings.push({ level: "warning", kind: "pending_dependency", subject: e.id, message: `Epic ${e.id} is blocked by unfinished dependency ${d3}.` });
21860
21873
  return findings;
21861
21874
  }
21875
+ function revisionOf(content) {
21876
+ return createHash3("sha256").update(content, "utf8").digest("hex").slice(0, 16);
21877
+ }
21862
21878
  var RESOURCE_ARTIFACT_TYPE2 = "project-resource";
21863
21879
  var REDACTED_COORDINATE2 = "\u2022\u2022\u2022\u2022 (hidden \u2014 store as an authentication reference)";
21864
21880
  var SECRET_KEY_RE2 = /(pass(word|phrase)?|secret|token|api[_-]?key|\bkey\b|credential|private|auth[_-]?token|access[_-]?key)/i;
@@ -21963,40 +21979,40 @@ function purposeFromBody3(body) {
21963
21979
  }
21964
21980
  function toDetail2(filePath, relPath, raw) {
21965
21981
  const parsed = matter52(raw);
21966
- const fm = parsed.data ?? {};
21982
+ const fm2 = parsed.data ?? {};
21967
21983
  const body = parsed.content.trim();
21968
- const id = str3(fm.id) ?? "";
21984
+ const id = str3(fm2.id) ?? "";
21969
21985
  return {
21970
21986
  id,
21971
- title: str3(fm.title) ?? id,
21972
- resourceType: str3(fm.resource_type),
21973
- provider: str3(fm.provider),
21974
- environments: strArray4(fm.environments),
21975
- scope: parseScope2(fm.scope),
21976
- modules: strArray4(fm.modules),
21987
+ title: str3(fm2.title) ?? id,
21988
+ resourceType: str3(fm2.resource_type),
21989
+ provider: str3(fm2.provider),
21990
+ environments: strArray4(fm2.environments),
21991
+ scope: parseScope2(fm2.scope),
21992
+ modules: strArray4(fm2.modules),
21977
21993
  path: relPath,
21978
- purpose: str3(fm.purpose) ?? purposeFromBody3(body),
21979
- interfaces: parseInterfaces2(fm.access_interfaces),
21980
- boundaries: parseBoundaries2(fm.access_boundaries),
21994
+ purpose: str3(fm2.purpose) ?? purposeFromBody3(body),
21995
+ interfaces: parseInterfaces2(fm2.access_interfaces),
21996
+ boundaries: parseBoundaries2(fm2.access_boundaries),
21981
21997
  ...(() => {
21982
- const a3 = parseAuth2(fm.authentication);
21998
+ const a3 = parseAuth2(fm2.authentication);
21983
21999
  return { authRefs: a3.refs, authMode: a3.mode };
21984
22000
  })(),
21985
- connectionProfiles: parseConnectionProfiles2(fm.connection_profiles),
22001
+ connectionProfiles: parseConnectionProfiles2(fm2.connection_profiles),
21986
22002
  markdownBody: body
21987
22003
  };
21988
22004
  }
21989
22005
  function getResources2(dir) {
21990
22006
  return discoverKnowledge2(dir).filter((a3) => a3.type === RESOURCE_ARTIFACT_TYPE2).map((a3) => {
21991
- const fm = a3.rawFrontmatter;
22007
+ const fm2 = a3.rawFrontmatter;
21992
22008
  return {
21993
- id: a3.id || str3(fm.id) || a3.title,
21994
- title: a3.title || str3(fm.title) || a3.id,
21995
- resourceType: str3(fm.resource_type),
21996
- provider: str3(fm.provider),
21997
- environments: strArray4(fm.environments),
21998
- scope: parseScope2(fm.scope),
21999
- modules: strArray4(fm.modules),
22009
+ id: a3.id || str3(fm2.id) || a3.title,
22010
+ title: a3.title || str3(fm2.title) || a3.id,
22011
+ resourceType: str3(fm2.resource_type),
22012
+ provider: str3(fm2.provider),
22013
+ environments: strArray4(fm2.environments),
22014
+ scope: parseScope2(fm2.scope),
22015
+ modules: strArray4(fm2.modules),
22000
22016
  path: a3.relPath
22001
22017
  };
22002
22018
  }).sort((x2, y) => x2.id.localeCompare(y.id));
@@ -22009,8 +22025,8 @@ function getResource2(dir, id) {
22009
22025
  const raw = readFile2(match.filePath);
22010
22026
  return toDetail2(match.filePath, match.relPath, raw);
22011
22027
  }
22012
- function parseWorkItemResources2(fm) {
22013
- const v = fm.resources;
22028
+ function parseWorkItemResources2(fm2) {
22029
+ const v = fm2.resources;
22014
22030
  if (!Array.isArray(v)) return [];
22015
22031
  const out = [];
22016
22032
  for (const raw of v) {
@@ -22034,8 +22050,8 @@ var EVIDENCE_SIGNALS2 = [
22034
22050
  function str22(v) {
22035
22051
  return typeof v === "string" && v.trim() ? v.trim() : null;
22036
22052
  }
22037
- function parseWorkItemDeprecationTargets2(fm) {
22038
- const v = fm.deprecation_targets;
22053
+ function parseWorkItemDeprecationTargets2(fm2) {
22054
+ const v = fm2.deprecation_targets;
22039
22055
  if (!Array.isArray(v)) return [];
22040
22056
  const out = [];
22041
22057
  for (const raw of v) {
@@ -22160,13 +22176,13 @@ function purposeFromBody22(body) {
22160
22176
  }
22161
22177
  function getDeprecationPlans2(dir) {
22162
22178
  return discoverKnowledge2(dir).filter((a3) => a3.type === DEPRECATION_ARTIFACT_TYPE2).map((a3) => {
22163
- const fm = a3.rawFrontmatter;
22164
- const targets = parseTargets2(fm.targets);
22179
+ const fm2 = a3.rawFrontmatter;
22180
+ const targets = parseTargets2(fm2.targets);
22165
22181
  return {
22166
- id: a3.id || str22(fm.id) || a3.title,
22167
- title: a3.title || str22(fm.title) || a3.id,
22182
+ id: a3.id || str22(fm2.id) || a3.title,
22183
+ title: a3.title || str22(fm2.title) || a3.id,
22168
22184
  path: a3.relPath,
22169
- status: str22(fm.status),
22185
+ status: str22(fm2.status),
22170
22186
  targetCount: targets.length,
22171
22187
  lifecycleCounts: lifecycleCountsOf2(targets)
22172
22188
  };
@@ -22179,17 +22195,17 @@ function getDeprecationPlan2(dir, id) {
22179
22195
  if (!match) return null;
22180
22196
  const raw = readFile2(match.filePath);
22181
22197
  const parsed = matter62(raw);
22182
- const fm = parsed.data ?? {};
22198
+ const fm2 = parsed.data ?? {};
22183
22199
  const body = parsed.content.trim();
22184
- const targets = parseTargets2(fm.targets);
22200
+ const targets = parseTargets2(fm2.targets);
22185
22201
  return {
22186
- id: str22(fm.id) ?? match.id,
22187
- title: str22(fm.title) ?? match.title,
22202
+ id: str22(fm2.id) ?? match.id,
22203
+ title: str22(fm2.title) ?? match.title,
22188
22204
  path: match.relPath,
22189
- status: str22(fm.status),
22205
+ status: str22(fm2.status),
22190
22206
  targetCount: targets.length,
22191
22207
  lifecycleCounts: lifecycleCountsOf2(targets),
22192
- purpose: str22(fm.purpose) ?? purposeFromBody22(body),
22208
+ purpose: str22(fm2.purpose) ?? purposeFromBody22(body),
22193
22209
  targets,
22194
22210
  markdownBody: body
22195
22211
  };
@@ -26206,7 +26222,7 @@ var AGENT_PROMPTS2 = [
26206
26222
  ].map((p) => ({ fileName: p.fileName, content: withResponsibilityTrace2(p.fileName, p.content) }));
26207
26223
  function withAgentFrontMatter2(fileName, content) {
26208
26224
  const name = fileName.replace(/\.md$/, "");
26209
- const fm = [
26225
+ const fm2 = [
26210
26226
  "---",
26211
26227
  "type: agent",
26212
26228
  `name: ${name}`,
@@ -26215,7 +26231,7 @@ function withAgentFrontMatter2(fileName, content) {
26215
26231
  "---",
26216
26232
  ""
26217
26233
  ].join("\n");
26218
- return fm + content.replace(/^\s+/, "");
26234
+ return fm2 + content.replace(/^\s+/, "");
26219
26235
  }
26220
26236
  var agentReadme2 = {
26221
26237
  path: "knowledge/agents/README.md",
@@ -26978,12 +26994,267 @@ function materializeCandidate(dir, id, candidateId) {
26978
26994
  if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after materialization.");
26979
26995
  return { workItemId: wiId, fileName, initiative: updated };
26980
26996
  }
26997
+ function normalizeCriterion(text22) {
26998
+ return text22.trim().toLowerCase().replace(/\s+/g, " ");
26999
+ }
27000
+ var STATUSES = ["passed", "failed", "not-verified", "manual-review-required"];
27001
+ function optStr42(v) {
27002
+ if (typeof v === "string" && v.trim()) return v.trim();
27003
+ if (typeof v === "number") return String(v);
27004
+ return void 0;
27005
+ }
27006
+ function parseEvidenceList(raw) {
27007
+ if (!Array.isArray(raw)) return [];
27008
+ const out = [];
27009
+ for (const entry of raw) {
27010
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
27011
+ const o3 = entry;
27012
+ const criterion = optStr42(o3.criterion);
27013
+ if (!criterion) continue;
27014
+ const statusRaw = optStr42(o3.status);
27015
+ const status = STATUSES.includes(statusRaw ?? "") ? statusRaw : "not-verified";
27016
+ const method = optStr42(o3.method);
27017
+ out.push({
27018
+ criterion,
27019
+ status,
27020
+ ...optStr42(o3.evidence) ? { evidence: optStr42(o3.evidence) } : {},
27021
+ ...optStr42(o3.reference) ? { reference: optStr42(o3.reference) } : {},
27022
+ ...method === "automated" || method === "manual" ? { method } : {},
27023
+ ...optStr42(o3.verified_at) ? { verified_at: optStr42(o3.verified_at) } : {},
27024
+ ...optStr42(o3.verified_by) ? { verified_by: optStr42(o3.verified_by) } : {}
27025
+ });
27026
+ }
27027
+ return out;
27028
+ }
27029
+ function parseExceptions(raw) {
27030
+ if (!Array.isArray(raw)) return [];
27031
+ const out = [];
27032
+ for (const entry of raw) {
27033
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
27034
+ const o3 = entry;
27035
+ const id = optStr42(o3.id);
27036
+ if (!id) continue;
27037
+ const st = optStr42(o3.status);
27038
+ const status = st === "accepted" || st === "rejected" ? st : "proposed";
27039
+ out.push({
27040
+ id,
27041
+ status,
27042
+ ...optStr42(o3.criterion) ? { criterion: optStr42(o3.criterion) } : {},
27043
+ ...optStr42(o3.reason) ? { reason: optStr42(o3.reason) } : {}
27044
+ });
27045
+ }
27046
+ return out;
27047
+ }
27048
+ function buildOutcomeVerification(declared, evidence, exceptions = []) {
27049
+ const byCriterion = /* @__PURE__ */ new Map();
27050
+ for (const e of evidence) byCriterion.set(normalizeCriterion(e.criterion), e);
27051
+ const declaredKeys = new Set(declared.map(normalizeCriterion));
27052
+ const counts = /* @__PURE__ */ new Map();
27053
+ for (const c3 of declared) counts.set(normalizeCriterion(c3), (counts.get(normalizeCriterion(c3)) ?? 0) + 1);
27054
+ const ambiguousKeys = new Set([...counts.entries()].filter(([, n2]) => n2 > 1).map(([k2]) => k2));
27055
+ const waived = new Set(
27056
+ exceptions.filter((x2) => x2.status === "accepted" && x2.reason?.trim() && x2.criterion && declaredKeys.has(normalizeCriterion(x2.criterion)) && !ambiguousKeys.has(normalizeCriterion(x2.criterion))).map((x2) => normalizeCriterion(x2.criterion))
27057
+ );
27058
+ const criteria = declared.map((criterion) => {
27059
+ const key = normalizeCriterion(criterion);
27060
+ const ambiguous = ambiguousKeys.has(key);
27061
+ const e = ambiguous ? void 0 : byCriterion.get(key);
27062
+ return {
27063
+ criterion,
27064
+ status: e?.status ?? "not-verified",
27065
+ ...e?.evidence ? { evidence: e.evidence } : {},
27066
+ ...e?.reference ? { reference: e.reference } : {},
27067
+ ...e?.method ? { method: e.method } : {},
27068
+ ...e?.verified_at ? { verifiedAt: e.verified_at } : {},
27069
+ ...e?.verified_by ? { verifiedBy: e.verified_by } : {},
27070
+ ...!ambiguous && waived.has(key) ? { waived: true } : {},
27071
+ ...ambiguous ? { ambiguous: true } : {}
27072
+ };
27073
+ });
27074
+ const stale = evidence.filter((e) => !declaredKeys.has(normalizeCriterion(e.criterion)));
27075
+ return {
27076
+ total: criteria.length,
27077
+ // An ambiguous criterion is never counted as passed/waived; it counts as unverified until disambiguated.
27078
+ passed: criteria.filter((c3) => !c3.ambiguous && (c3.status === "passed" || c3.waived)).length,
27079
+ failed: criteria.filter((c3) => !c3.ambiguous && c3.status === "failed" && !c3.waived).length,
27080
+ notVerified: criteria.filter((c3) => c3.ambiguous || c3.status === "not-verified" && !c3.waived).length,
27081
+ manualReview: criteria.filter((c3) => !c3.ambiguous && c3.status === "manual-review-required" && !c3.waived).length,
27082
+ ambiguous: criteria.filter((c3) => c3.ambiguous).length,
27083
+ criteria,
27084
+ stale
27085
+ };
27086
+ }
27087
+ function fm(entity, key) {
27088
+ return entity.rawFrontmatter?.[key];
27089
+ }
27090
+ function epicOutcomeVerification(epic) {
27091
+ return buildOutcomeVerification(
27092
+ epic.successCriteria,
27093
+ parseEvidenceList(fm(epic, "outcome_verifications")),
27094
+ parseExceptions(fm(epic, "completion_exceptions"))
27095
+ );
27096
+ }
27097
+ function planOutcomeVerification(plan) {
27098
+ return buildOutcomeVerification(
27099
+ plan.successCriteria,
27100
+ parseEvidenceList(fm(plan, "outcome_verifications")),
27101
+ parseExceptions(fm(plan, "completion_exceptions"))
27102
+ );
27103
+ }
27104
+ function planMilestoneVerifications(plan) {
27105
+ const rawMs = Array.isArray(fm(plan, "milestones")) ? fm(plan, "milestones") : [];
27106
+ return plan.milestones.map((m2) => {
27107
+ const rawEntry = rawMs.find((r2) => r2 && typeof r2 === "object" && optStr42(r2.id)?.toLowerCase() === m2.id.toLowerCase());
27108
+ const evidence = parseEvidenceList(rawEntry?.exit_verifications);
27109
+ return { id: m2.id, title: m2.title, outcome: buildOutcomeVerification(m2.exitCriteria, evidence) };
27110
+ });
27111
+ }
27112
+ function decide(blockers, softBlocks, hasAcceptedExceptions) {
27113
+ if (blockers.length > 0) return "BLOCKED";
27114
+ if (softBlocks.length > 0) return "NEEDS_WORK";
27115
+ return hasAcceptedExceptions ? "READY_WITH_EXCEPTIONS" : "READY_TO_COMPLETE";
27116
+ }
27117
+ function evaluateEpicCompletion(dir, epic) {
27118
+ const prog = computeEpicProgress(dir, epic);
27119
+ const exceptions = parseExceptions(fm(epic, "completion_exceptions"));
27120
+ const outcome = epicOutcomeVerification(epic);
27121
+ const blockers = [];
27122
+ const softBlocks = [];
27123
+ const reasons = [];
27124
+ const warnings = [];
27125
+ for (const d3 of prog.readiness.blockingDependencies) blockers.push(`Blocked by unfinished dependency ${d3}.`);
27126
+ for (const f3 of integrityBlockers(dir, epic.id)) blockers.push(f3);
27127
+ evaluateOutcomeInto(epic.outcome, true, outcome, blockers, softBlocks, reasons, warnings);
27128
+ if (prog.delivery.total > 0 && prog.delivery.completed < prog.delivery.total) {
27129
+ softBlocks.push("committed-work-incomplete");
27130
+ reasons.push(`Committed work is incomplete (${prog.delivery.completed}/${prog.delivery.total} Work Items completed).`);
27131
+ }
27132
+ const readiness = decide(blockers, softBlocks, exceptions.some((x2) => x2.status === "accepted"));
27133
+ pushReadinessReason(readiness, reasons);
27134
+ return {
27135
+ id: epic.id,
27136
+ kind: "epic",
27137
+ status: epic.status,
27138
+ readiness,
27139
+ reasons,
27140
+ blockers,
27141
+ warnings,
27142
+ outcome,
27143
+ delivery: { total: prog.delivery.total, completed: prog.delivery.completed, blocked: prog.delivery.blocked },
27144
+ blockingDependencies: prog.readiness.blockingDependencies,
27145
+ exceptions
27146
+ };
27147
+ }
27148
+ function evaluatePlanCompletion(dir, plan) {
27149
+ const prog = computePlanProgress(dir, plan);
27150
+ const exceptions = parseExceptions(fm(plan, "completion_exceptions"));
27151
+ const outcome = planOutcomeVerification(plan);
27152
+ const milestones = planMilestoneVerifications(plan);
27153
+ const epics = epicsInPlan(dir, plan.id).map((e) => ({ id: e.id, status: e.status }));
27154
+ const blockers = [];
27155
+ const softBlocks = [];
27156
+ const reasons = [];
27157
+ const warnings = [];
27158
+ for (const d3 of prog.readiness.blockingDependencies) blockers.push(`Blocked by unfinished dependency ${d3}.`);
27159
+ for (const f3 of integrityBlockers(dir, plan.id)) blockers.push(f3);
27160
+ evaluateOutcomeInto(plan.objective, false, outcome, blockers, softBlocks, reasons, warnings);
27161
+ if (!plan.objective) warnings.push("No objective is declared for this Plan (optional, but recommended for completion review).");
27162
+ if (prog.delivery.total > 0 && prog.delivery.completed < prog.delivery.total) {
27163
+ softBlocks.push("committed-work-incomplete");
27164
+ reasons.push(`Committed work is incomplete (${prog.delivery.completed}/${prog.delivery.total} Work Items completed).`);
27165
+ }
27166
+ if (epics.length === 0 && prog.delivery.total === 0) {
27167
+ softBlocks.push("no-committed-work");
27168
+ reasons.push("No Epics and no committed Work Items \u2014 the Plan has no verifiable deliverables yet.");
27169
+ }
27170
+ const openEpics = epics.filter((e) => e.status !== "completed" && e.status !== "cancelled");
27171
+ if (openEpics.length > 0) {
27172
+ softBlocks.push("epics-open");
27173
+ reasons.push(`${openEpics.length} Epic(s) not yet completed: ${openEpics.map((e) => e.id).join(", ")}.`);
27174
+ }
27175
+ for (const m2 of milestones) {
27176
+ if (m2.outcome.failed > 0) {
27177
+ blockers.push(`Milestone ${m2.id} has a failed exit criterion.`);
27178
+ }
27179
+ const pending = m2.outcome.notVerified + m2.outcome.manualReview;
27180
+ if (pending > 0) {
27181
+ softBlocks.push(`milestone-${m2.id}-pending`);
27182
+ reasons.push(`Milestone ${m2.id}: ${pending} exit criterion(s) not verified.`);
27183
+ }
27184
+ if (m2.outcome.stale.length > 0) warnings.push(`Milestone ${m2.id} has stale exit-criteria evidence.`);
27185
+ }
27186
+ const readiness = decide(blockers, softBlocks, exceptions.some((x2) => x2.status === "accepted"));
27187
+ pushReadinessReason(readiness, reasons);
27188
+ return {
27189
+ id: plan.id,
27190
+ kind: "plan",
27191
+ status: plan.status,
27192
+ readiness,
27193
+ reasons,
27194
+ blockers,
27195
+ warnings,
27196
+ outcome,
27197
+ delivery: { total: prog.delivery.total, completed: prog.delivery.completed, blocked: prog.delivery.blocked },
27198
+ blockingDependencies: prog.readiness.blockingDependencies,
27199
+ milestones,
27200
+ epics,
27201
+ exceptions
27202
+ };
27203
+ }
27204
+ function integrityBlockers(dir, subjectId) {
27205
+ return validatePlanningGraph(dir).filter((f3) => f3.level === "error" && f3.subject.toLowerCase() === subjectId.toLowerCase()).map((f3) => f3.message);
27206
+ }
27207
+ function evaluateOutcomeInto(declaredOutcome, requireDeclaredOutcome, outcome, blockers, softBlocks, reasons, warnings) {
27208
+ if (requireDeclaredOutcome && !declaredOutcome) {
27209
+ softBlocks.push("no-outcome");
27210
+ reasons.push("No expected outcome is declared.");
27211
+ }
27212
+ if (outcome.total === 0) {
27213
+ softBlocks.push("no-success-criteria");
27214
+ reasons.push("No success criteria to verify \u2014 completion cannot be judged by outcome.");
27215
+ }
27216
+ if (outcome.ambiguous > 0) {
27217
+ softBlocks.push("criteria-ambiguous");
27218
+ reasons.push(`${outcome.ambiguous} success criterion(s) collide in text \u2014 rename to disambiguate before verifying.`);
27219
+ }
27220
+ if (outcome.failed > 0) {
27221
+ softBlocks.push("criteria-failed");
27222
+ reasons.push(`${outcome.failed} success criterion(s) failed verification.`);
27223
+ }
27224
+ const pending = outcome.notVerified + outcome.manualReview - outcome.ambiguous;
27225
+ if (pending > 0) {
27226
+ softBlocks.push("criteria-unverified");
27227
+ reasons.push(`${pending} success criterion(s) not verified (no or insufficient evidence).`);
27228
+ }
27229
+ if (outcome.stale.length > 0) warnings.push(`${outcome.stale.length} evidence record(s) no longer match a declared criterion (stale).`);
27230
+ }
27231
+ function pushReadinessReason(readiness, reasons) {
27232
+ if (readiness === "READY_TO_COMPLETE") reasons.unshift("All success criteria are verified and committed work is complete. Human approval is still required to close.");
27233
+ else if (readiness === "READY_WITH_EXCEPTIONS") reasons.unshift("Remaining gaps are covered by accepted exceptions. Human approval is still required to close.");
27234
+ }
26981
27235
  var DeliveryPlanningWriteError = class extends Error {
26982
27236
  constructor(message) {
26983
27237
  super(message);
26984
27238
  this.name = "DeliveryPlanningWriteError";
26985
27239
  }
26986
27240
  };
27241
+ var PlanningConflictError = class extends Error {
27242
+ constructor(id, expectedRevision, actualRevision) {
27243
+ super(`"${id}" was modified since it was loaded (expected revision ${expectedRevision}, found ${actualRevision}). Reload and try again.`);
27244
+ this.id = id;
27245
+ this.expectedRevision = expectedRevision;
27246
+ this.actualRevision = actualRevision;
27247
+ this.name = "PlanningConflictError";
27248
+ }
27249
+ id;
27250
+ expectedRevision;
27251
+ actualRevision;
27252
+ };
27253
+ function assertFreshRevision(id, currentContent, opts) {
27254
+ if (!opts?.expectedRevision) return;
27255
+ const actual = revisionOf(currentContent);
27256
+ if (actual !== opts.expectedRevision) throw new PlanningConflictError(id, opts.expectedRevision, actual);
27257
+ }
26987
27258
  function slugify3(s) {
26988
27259
  return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "item";
26989
27260
  }
@@ -27044,6 +27315,163 @@ function associateInitiativeWithEpic(dir, initiativeId, epicId) {
27044
27315
  if (!getEpic(dir, epicId)) throw new DeliveryPlanningWriteError(`Epic ${epicId} does not exist.`);
27045
27316
  return updateInitiative(dir, ini.id, { epic: epicId });
27046
27317
  }
27318
+ function today22() {
27319
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
27320
+ }
27321
+ var PlanningNotReadyError = class extends DeliveryPlanningWriteError {
27322
+ constructor(evaluation) {
27323
+ super(`${evaluation.kind === "plan" ? "Plan" : "Epic"} ${evaluation.id} is not ready to complete (${evaluation.readiness}).`);
27324
+ this.evaluation = evaluation;
27325
+ this.name = "PlanningNotReadyError";
27326
+ }
27327
+ evaluation;
27328
+ };
27329
+ function transitionDeliveryPlan(dir, id, to, opts) {
27330
+ const plan = getPlan(dir, id);
27331
+ if (!plan) throw new DeliveryPlanningWriteError(`Plan ${id} does not exist.`);
27332
+ if (!PLAN_STATES2.includes(to)) throw new DeliveryPlanningWriteError(`Invalid plan status: ${to}.`);
27333
+ if (to === "completed") throw new DeliveryPlanningWriteError(`Completing Plan ${id} requires evidence-based review \u2014 use completeDeliveryPlan.`);
27334
+ const raw = readFile2(plan.filePath);
27335
+ assertFreshRevision(plan.id, raw, opts);
27336
+ if (plan.status === to) return plan;
27337
+ if (!PLAN_TRANSITIONS[plan.status].includes(to)) throw new DeliveryPlanningWriteError(`Invalid transition ${plan.status} \u2192 ${to} for Plan ${id}.`);
27338
+ const { data, content } = matter132(raw, {});
27339
+ data.status = to;
27340
+ if (to === "completed") data.completed_at = today22();
27341
+ const newContent = matter132.stringify(content, data);
27342
+ writeFile2(plan.filePath, newContent);
27343
+ const updated = parseDeliveryPlan2(dir, plan.filePath, newContent);
27344
+ if (!updated) throw new DeliveryPlanningWriteError("Failed to parse the Plan after transition.");
27345
+ return updated;
27346
+ }
27347
+ function transitionEpic(dir, id, to, opts) {
27348
+ const epic = getEpic(dir, id);
27349
+ if (!epic) throw new DeliveryPlanningWriteError(`Epic ${id} does not exist.`);
27350
+ if (!EPIC_STATES2.includes(to)) throw new DeliveryPlanningWriteError(`Invalid epic status: ${to}.`);
27351
+ if (to === "completed") throw new DeliveryPlanningWriteError(`Completing Epic ${id} requires evidence-based review \u2014 use completeEpic.`);
27352
+ const raw = readFile2(epic.filePath);
27353
+ assertFreshRevision(epic.id, raw, opts);
27354
+ if (epic.status === to) return epic;
27355
+ if (!EPIC_TRANSITIONS[epic.status].includes(to)) throw new DeliveryPlanningWriteError(`Invalid transition ${epic.status} \u2192 ${to} for Epic ${id}.`);
27356
+ const { data, content } = matter132(raw, {});
27357
+ data.status = to;
27358
+ if (to === "completed") data.completed_at = today22();
27359
+ const newContent = matter132.stringify(content, data);
27360
+ writeFile2(epic.filePath, newContent);
27361
+ const updated = parseEpic2(dir, epic.filePath, newContent);
27362
+ if (!updated) throw new DeliveryPlanningWriteError("Failed to parse the Epic after transition.");
27363
+ return updated;
27364
+ }
27365
+ function assertCriterionDeclared(declared, criterion, kind) {
27366
+ const key = normalizeCriterion(criterion);
27367
+ const matches = declared.filter((c3) => normalizeCriterion(c3) === key).length;
27368
+ if (matches === 0) throw new DeliveryPlanningWriteError(`"${criterion}" is not a declared ${kind} criterion.`);
27369
+ if (matches > 1) throw new DeliveryPlanningWriteError(`"${criterion}" matches more than one declared ${kind} criterion \u2014 rename them to disambiguate before recording evidence.`);
27370
+ }
27371
+ function upsertEvidence(existing, input) {
27372
+ const list22 = Array.isArray(existing) ? existing.filter((e) => e && typeof e === "object" && !Array.isArray(e)) : [];
27373
+ const key = normalizeCriterion(input.criterion);
27374
+ const kept = list22.filter((e) => normalizeCriterion(String(e.criterion ?? "")) !== key);
27375
+ const obj = { criterion: input.criterion.trim(), status: input.status };
27376
+ if (input.evidence?.trim()) obj.evidence = input.evidence.trim();
27377
+ if (input.reference?.trim()) obj.reference = input.reference.trim();
27378
+ if (input.method) obj.method = input.method;
27379
+ obj.verified_at = today22();
27380
+ if (input.verifiedBy?.trim()) obj.verified_by = input.verifiedBy.trim();
27381
+ return [...kept, obj];
27382
+ }
27383
+ function recordEpicOutcomeVerification(dir, id, input, opts) {
27384
+ const epic = getEpic(dir, id);
27385
+ if (!epic) throw new DeliveryPlanningWriteError(`Epic ${id} does not exist.`);
27386
+ assertCriterionDeclared(epic.successCriteria, input.criterion, "success");
27387
+ const raw = readFile2(epic.filePath);
27388
+ assertFreshRevision(epic.id, raw, opts);
27389
+ const { data, content } = matter132(raw, {});
27390
+ data.outcome_verifications = upsertEvidence(data.outcome_verifications, input);
27391
+ const newContent = matter132.stringify(content, data);
27392
+ writeFile2(epic.filePath, newContent);
27393
+ const updated = parseEpic2(dir, epic.filePath, newContent);
27394
+ if (!updated) throw new DeliveryPlanningWriteError("Failed to parse the Epic after recording evidence.");
27395
+ return updated;
27396
+ }
27397
+ function recordPlanOutcomeVerification(dir, id, input, opts) {
27398
+ const plan = getPlan(dir, id);
27399
+ if (!plan) throw new DeliveryPlanningWriteError(`Plan ${id} does not exist.`);
27400
+ assertCriterionDeclared(plan.successCriteria, input.criterion, "success");
27401
+ const raw = readFile2(plan.filePath);
27402
+ assertFreshRevision(plan.id, raw, opts);
27403
+ const { data, content } = matter132(raw, {});
27404
+ data.outcome_verifications = upsertEvidence(data.outcome_verifications, input);
27405
+ const newContent = matter132.stringify(content, data);
27406
+ writeFile2(plan.filePath, newContent);
27407
+ const updated = parseDeliveryPlan2(dir, plan.filePath, newContent);
27408
+ if (!updated) throw new DeliveryPlanningWriteError("Failed to parse the Plan after recording evidence.");
27409
+ return updated;
27410
+ }
27411
+ function recordMilestoneExitVerification(dir, planId, milestoneId, input, opts) {
27412
+ const plan = getPlan(dir, planId);
27413
+ if (!plan) throw new DeliveryPlanningWriteError(`Plan ${planId} does not exist.`);
27414
+ const milestone = plan.milestones.find((m2) => m2.id.toLowerCase() === milestoneId.toLowerCase());
27415
+ if (!milestone) throw new DeliveryPlanningWriteError(`Milestone ${milestoneId} is not defined in ${planId}.`);
27416
+ assertCriterionDeclared(milestone.exitCriteria, input.criterion, "exit");
27417
+ const raw = readFile2(plan.filePath);
27418
+ assertFreshRevision(plan.id, raw, opts);
27419
+ const { data, content } = matter132(raw, {});
27420
+ const rawMs = Array.isArray(data.milestones) ? data.milestones : [];
27421
+ const entry = rawMs.find((m2) => m2 && typeof m2 === "object" && String(m2.id ?? "").toLowerCase() === milestoneId.toLowerCase());
27422
+ if (!entry) throw new DeliveryPlanningWriteError(`Milestone ${milestoneId} entry not found in ${planId} frontmatter.`);
27423
+ entry.exit_verifications = upsertEvidence(entry.exit_verifications, input);
27424
+ data.milestones = rawMs;
27425
+ const newContent = matter132.stringify(content, data);
27426
+ writeFile2(plan.filePath, newContent);
27427
+ const updated = parseDeliveryPlan2(dir, plan.filePath, newContent);
27428
+ if (!updated) throw new DeliveryPlanningWriteError("Failed to parse the Plan after recording milestone evidence.");
27429
+ return updated;
27430
+ }
27431
+ function completionDecisionRecord(evaluation, input) {
27432
+ const rec = { readiness: evaluation.readiness, decided_at: today22(), reasons: evaluation.reasons };
27433
+ if (input.decidedBy?.trim()) rec.decided_by = input.decidedBy.trim();
27434
+ if (input.note?.trim()) rec.note = input.note.trim();
27435
+ const acceptedExceptions = evaluation.exceptions.filter((x2) => x2.status === "accepted");
27436
+ if (acceptedExceptions.length > 0) rec.exceptions = acceptedExceptions;
27437
+ return rec;
27438
+ }
27439
+ function completeEpic(dir, id, input = {}, opts) {
27440
+ const epic = getEpic(dir, id);
27441
+ if (!epic) throw new DeliveryPlanningWriteError(`Epic ${id} does not exist.`);
27442
+ const evaluation = evaluateEpicCompletion(dir, epic);
27443
+ if (evaluation.readiness !== "READY_TO_COMPLETE" && evaluation.readiness !== "READY_WITH_EXCEPTIONS") throw new PlanningNotReadyError(evaluation);
27444
+ if (!EPIC_TRANSITIONS[epic.status].includes("completed")) throw new DeliveryPlanningWriteError(`Epic ${id} cannot move from ${epic.status} to completed.`);
27445
+ const raw = readFile2(epic.filePath);
27446
+ assertFreshRevision(epic.id, raw, opts);
27447
+ const { data, content } = matter132(raw, {});
27448
+ data.status = "completed";
27449
+ data.completed_at = today22();
27450
+ data.completion_decision = completionDecisionRecord(evaluation, input);
27451
+ const newContent = matter132.stringify(content, data);
27452
+ writeFile2(epic.filePath, newContent);
27453
+ const updated = parseEpic2(dir, epic.filePath, newContent);
27454
+ if (!updated) throw new DeliveryPlanningWriteError("Failed to parse the Epic after completion.");
27455
+ return updated;
27456
+ }
27457
+ function completeDeliveryPlan(dir, id, input = {}, opts) {
27458
+ const plan = getPlan(dir, id);
27459
+ if (!plan) throw new DeliveryPlanningWriteError(`Plan ${id} does not exist.`);
27460
+ const evaluation = evaluatePlanCompletion(dir, plan);
27461
+ if (evaluation.readiness !== "READY_TO_COMPLETE" && evaluation.readiness !== "READY_WITH_EXCEPTIONS") throw new PlanningNotReadyError(evaluation);
27462
+ if (!PLAN_TRANSITIONS[plan.status].includes("completed")) throw new DeliveryPlanningWriteError(`Plan ${id} cannot move from ${plan.status} to completed.`);
27463
+ const raw = readFile2(plan.filePath);
27464
+ assertFreshRevision(plan.id, raw, opts);
27465
+ const { data, content } = matter132(raw, {});
27466
+ data.status = "completed";
27467
+ data.completed_at = today22();
27468
+ data.completion_decision = completionDecisionRecord(evaluation, input);
27469
+ const newContent = matter132.stringify(content, data);
27470
+ writeFile2(plan.filePath, newContent);
27471
+ const updated = parseDeliveryPlan2(dir, plan.filePath, newContent);
27472
+ if (!updated) throw new DeliveryPlanningWriteError("Failed to parse the Plan after completion.");
27473
+ return updated;
27474
+ }
27047
27475
  var USER_FACING_KEYWORDS_EN = [
27048
27476
  "user",
27049
27477
  "visitor",
@@ -27356,7 +27784,7 @@ function getWorkItem(dir, workItemId) {
27356
27784
  const base = toListItem(match);
27357
27785
  const body = readBody(match.filePath);
27358
27786
  const sections = splitSections(body);
27359
- const fm = match.rawFrontmatter;
27787
+ const fm2 = match.rawFrontmatter;
27360
27788
  const knowledge = discoverKnowledge2(dir).filter((a3) => !a3.isWorkItem);
27361
27789
  const knowledgeById = new Map(knowledge.filter((k2) => k2.id).map((k2) => [k2.id, k2]));
27362
27790
  const detail = {
@@ -27373,21 +27801,21 @@ function getWorkItem(dir, workItemId) {
27373
27801
  moduleCoverage: normalizeCoverage(match.moduleCoverage),
27374
27802
  impactAnalysis: normalizeImpact(match.impactAnalysis),
27375
27803
  acceptanceCriteria: parseAcceptanceCriteria(sections),
27376
- implementationEvidence: parseEvidence(fm),
27377
- releaseGates: parseReleaseGates(fm),
27378
- completionExceptions: parseExceptions(fm),
27804
+ implementationEvidence: parseEvidence(fm2),
27805
+ releaseGates: parseReleaseGates(fm2),
27806
+ completionExceptions: parseExceptions2(fm2),
27379
27807
  decisions: parseDecisions(match.decisions, knowledgeById),
27380
- relatedKnowledge: parseRelatedKnowledge(fm, knowledgeById),
27381
- source: parseWorkItemSource2(fm),
27382
- originalSnapshot: parseOriginalSnapshot(fm),
27808
+ relatedKnowledge: parseRelatedKnowledge(fm2, knowledgeById),
27809
+ source: parseWorkItemSource2(fm2),
27810
+ originalSnapshot: parseOriginalSnapshot(fm2),
27383
27811
  path: match.relPath,
27384
27812
  // Faithful source of truth: the artifact body already read above, not reconstructed (WI-025).
27385
27813
  markdownBody: body.trim(),
27386
27814
  // External systems this WI relates to (WI-030), separate from affectedModules (code).
27387
- resources: parseWorkItemResources2(fm),
27815
+ resources: parseWorkItemResources2(fm2),
27388
27816
  // Deprecation Targets this WI retires (WI-050), referenced not copied.
27389
- deprecationTargets: parseWorkItemDeprecationTargets2(fm),
27390
- ...parseSystemImpact(dir, fm)
27817
+ deprecationTargets: parseWorkItemDeprecationTargets2(fm2),
27818
+ ...parseSystemImpact(dir, fm2)
27391
27819
  };
27392
27820
  return { ...detail, refinement: computeRefinementStatus(detail) };
27393
27821
  }
@@ -27410,14 +27838,14 @@ function parseExplain(o3) {
27410
27838
  function emptyExplain() {
27411
27839
  return { reason: null, graphReason: null, evidenceRefs: [], evidenceSummary: null };
27412
27840
  }
27413
- function parseSystemImpact(dir, fm) {
27841
+ function parseSystemImpact(dir, fm2) {
27414
27842
  const topology = loadSystemTopology(dir);
27415
27843
  const byId = new Map(topology.entities.map((e) => [e.id, e]));
27416
27844
  const resolve = (id) => {
27417
27845
  const e = byId.get(id);
27418
27846
  return { id, nodeId: `sys:${id}`, label: e?.label ?? id, kind: e?.kind ?? "unknown", moduleId: e?.moduleId ?? null };
27419
27847
  };
27420
- const affected = Array.isArray(fm.affected_system_entities) ? fm.affected_system_entities.map((raw) => {
27848
+ const affected = Array.isArray(fm2.affected_system_entities) ? fm2.affected_system_entities.map((raw) => {
27421
27849
  if (typeof raw === "string") return raw ? { ...resolve(raw), ...emptyExplain() } : null;
27422
27850
  if (raw && typeof raw === "object") {
27423
27851
  const o3 = raw;
@@ -27426,13 +27854,13 @@ function parseSystemImpact(dir, fm) {
27426
27854
  }
27427
27855
  return null;
27428
27856
  }).filter((e) => e != null) : [];
27429
- const reviewed = Array.isArray(fm.reviewed_system_entities) ? fm.reviewed_system_entities.filter((r2) => Boolean(r2) && typeof r2 === "object").map((r2) => ({ ...resolve(String(r2.id ?? "")), ...parseExplain(r2), status: String(r2.status ?? "unknown") })).filter((r2) => r2.id) : [];
27430
- const graphRevision = typeof fm.graph_revision === "string" && fm.graph_revision.trim() ? fm.graph_revision.trim() : null;
27857
+ const reviewed = Array.isArray(fm2.reviewed_system_entities) ? fm2.reviewed_system_entities.filter((r2) => Boolean(r2) && typeof r2 === "object").map((r2) => ({ ...resolve(String(r2.id ?? "")), ...parseExplain(r2), status: String(r2.status ?? "unknown") })).filter((r2) => r2.id) : [];
27858
+ const graphRevision = typeof fm2.graph_revision === "string" && fm2.graph_revision.trim() ? fm2.graph_revision.trim() : null;
27431
27859
  const graphCoverage = topology.entities.length === 0 ? "unavailable" : topology.relationships.length > 0 ? "available" : "partial";
27432
27860
  return { affectedSystemEntities: affected, reviewedSystemEntities: reviewed, graphRevision, graphCoverage };
27433
27861
  }
27434
- function parseOriginalSnapshot(fm) {
27435
- const raw = fm.original_snapshot;
27862
+ function parseOriginalSnapshot(fm2) {
27863
+ const raw = fm2.original_snapshot;
27436
27864
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
27437
27865
  const o3 = raw;
27438
27866
  const title = typeof o3.title === "string" ? o3.title : void 0;
@@ -27535,12 +27963,12 @@ function normalizeImpact(ia) {
27535
27963
  ...v.question ? { question: v.question } : {}
27536
27964
  }));
27537
27965
  }
27538
- function optStr42(v) {
27966
+ function optStr52(v) {
27539
27967
  if (typeof v === "string" && v.trim()) return v.trim();
27540
27968
  return void 0;
27541
27969
  }
27542
- function parseEvidence(fm) {
27543
- const evidence = fm.implementation_evidence;
27970
+ function parseEvidence(fm2) {
27971
+ const evidence = fm2.implementation_evidence;
27544
27972
  const repos = evidence?.repositories;
27545
27973
  if (!repos || typeof repos !== "object" || Array.isArray(repos)) return [];
27546
27974
  const out = [];
@@ -27550,13 +27978,13 @@ function parseEvidence(fm) {
27550
27978
  const validations = Array.isArray(d3.validations) ? d3.validations.map((v) => ({
27551
27979
  command: String(v.command ?? ""),
27552
27980
  status: String(v.status ?? "unknown"),
27553
- ...optStr42(v.reason) ? { reason: optStr42(v.reason) } : {}
27981
+ ...optStr52(v.reason) ? { reason: optStr52(v.reason) } : {}
27554
27982
  })) : [];
27555
27983
  const migrations = Array.isArray(d3.migrations) ? d3.migrations.map((m2) => ({
27556
27984
  id: String(m2.id ?? ""),
27557
27985
  environment: String(m2.environment ?? ""),
27558
27986
  status: String(m2.status ?? "unknown"),
27559
- ...optStr42(m2.reason) ? { reason: optStr42(m2.reason) } : {}
27987
+ ...optStr52(m2.reason) ? { reason: optStr52(m2.reason) } : {}
27560
27988
  })) : [];
27561
27989
  out.push({
27562
27990
  module,
@@ -27569,23 +27997,23 @@ function parseEvidence(fm) {
27569
27997
  }
27570
27998
  return out;
27571
27999
  }
27572
- function parseReleaseGates(fm) {
27573
- if (!Array.isArray(fm.release_gates)) return [];
27574
- return fm.release_gates.filter((g) => g && typeof g === "object").map((g) => ({
28000
+ function parseReleaseGates(fm2) {
28001
+ if (!Array.isArray(fm2.release_gates)) return [];
28002
+ return fm2.release_gates.filter((g) => g && typeof g === "object").map((g) => ({
27575
28003
  id: String(g.id ?? ""),
27576
28004
  status: String(g.status ?? "pending"),
27577
- ...optStr42(g.reason) ? { reason: optStr42(g.reason) } : {},
27578
- ...optStr42(g.required_for) ? { requiredFor: optStr42(g.required_for) } : {}
28005
+ ...optStr52(g.reason) ? { reason: optStr52(g.reason) } : {},
28006
+ ...optStr52(g.required_for) ? { requiredFor: optStr52(g.required_for) } : {}
27579
28007
  }));
27580
28008
  }
27581
- function parseExceptions(fm) {
27582
- if (!Array.isArray(fm.completion_exceptions)) return [];
27583
- return fm.completion_exceptions.filter((e) => e && typeof e === "object").map((e) => ({
28009
+ function parseExceptions2(fm2) {
28010
+ if (!Array.isArray(fm2.completion_exceptions)) return [];
28011
+ return fm2.completion_exceptions.filter((e) => e && typeof e === "object").map((e) => ({
27584
28012
  id: String(e.id ?? ""),
27585
28013
  status: String(e.status ?? "proposed"),
27586
- ...optStr42(e.reason) ? { reason: optStr42(e.reason) } : {},
27587
- ...optStr42(e.category) ? { category: optStr42(e.category) } : {},
27588
- ...optStr42(e.impact) ? { impact: optStr42(e.impact) } : {}
28014
+ ...optStr52(e.reason) ? { reason: optStr52(e.reason) } : {},
28015
+ ...optStr52(e.category) ? { category: optStr52(e.category) } : {},
28016
+ ...optStr52(e.impact) ? { impact: optStr52(e.impact) } : {}
27589
28017
  }));
27590
28018
  }
27591
28019
  function parseDecisions(decisions, knowledgeById) {
@@ -27598,10 +28026,10 @@ function parseDecisions(decisions, knowledgeById) {
27598
28026
  };
27599
28027
  });
27600
28028
  }
27601
- function parseRelatedKnowledge(fm, knowledgeById) {
28029
+ function parseRelatedKnowledge(fm2, knowledgeById) {
27602
28030
  const refs = /* @__PURE__ */ new Set();
27603
28031
  for (const field of ["related_knowledge", "knowledge", "related"]) {
27604
- const v = fm[field];
28032
+ const v = fm2[field];
27605
28033
  if (Array.isArray(v)) {
27606
28034
  for (const r2 of v) if (typeof r2 === "string" && r2.trim()) refs.add(r2.trim());
27607
28035
  }
@@ -27769,7 +28197,7 @@ function createWorkItem(dir, opts) {
27769
28197
  if (!type) throw new WorkItemWriteError("INVALID_INPUT", `Unknown Work Item type "${opts.type}".`);
27770
28198
  const id = nextWorkItemId2(dir);
27771
28199
  const title = intent.split(/\r?\n/)[0].trim().slice(0, 120);
27772
- const today22 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
28200
+ const today3 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
27773
28201
  const summary = opts.summary?.trim() || intent;
27774
28202
  const source22 = opts.source ? { ...opts.source, inferred: false } : { type: "manual", inferred: false };
27775
28203
  const data = {
@@ -27778,7 +28206,7 @@ function createWorkItem(dir, opts) {
27778
28206
  title,
27779
28207
  status: "draft",
27780
28208
  work_type: type,
27781
- created_at: today22,
28209
+ created_at: today3,
27782
28210
  source: source22,
27783
28211
  generated_by: "kaddo-admin",
27784
28212
  affected_modules: [],
@@ -28469,11 +28897,11 @@ function verifyWorkItem(dir, workItemId, input) {
28469
28897
  const artifacts = discoverWorkItems2(dir);
28470
28898
  const match = artifacts.find((a3) => (a3.id || a3.title) === workItemId);
28471
28899
  const lifecycle = lifecycleStateOf2({ status: match.status, filePath: match.filePath });
28472
- const fm = match.rawFrontmatter;
28900
+ const fm2 = match.rawFrontmatter;
28473
28901
  const acVerifications = mapACVerifications(input.acVerifications);
28474
28902
  const acSummary = computeACSummary(acVerifications);
28475
- const releaseGates = Array.isArray(fm.release_gates) ? fm.release_gates : [];
28476
- const completionExceptions = Array.isArray(fm.completion_exceptions) ? fm.completion_exceptions : [];
28903
+ const releaseGates = Array.isArray(fm2.release_gates) ? fm2.release_gates : [];
28904
+ const completionExceptions = Array.isArray(fm2.completion_exceptions) ? fm2.completion_exceptions : [];
28477
28905
  const plannedVsActual = computePlannedVsActual(match.affectedModules, input.repos);
28478
28906
  const registeredModuleIds = loadMappedModules2(dir).map((m2) => m2.id);
28479
28907
  const modifiedRepoIds = input.repos.map((r2) => r2.repoId);
@@ -28484,7 +28912,7 @@ function verifyWorkItem(dir, workItemId, input) {
28484
28912
  validationStatus: match.validationStatus || "not-started",
28485
28913
  releaseStatus: match.releaseStatus || "not-assessed",
28486
28914
  affectedModules: match.affectedModules,
28487
- rawFrontmatter: fm,
28915
+ rawFrontmatter: fm2,
28488
28916
  registeredModuleIds,
28489
28917
  modifiedRepoIds
28490
28918
  });
@@ -31264,10 +31692,10 @@ function modulesDiscoverTool(root, args) {
31264
31692
  return ok2({ discovered, applied: false });
31265
31693
  }
31266
31694
  function renderCapsuleMd(data) {
31267
- const fm = ["---", `type: knowledge-capsule`, `system: ${data.system}`, `version: ${data.version}`, `updated_at: ${data.updated_at}`, "---", ""].join("\n");
31695
+ const fm2 = ["---", `type: knowledge-capsule`, `system: ${data.system}`, `version: ${data.version}`, `updated_at: ${data.updated_at}`, "---", ""].join("\n");
31268
31696
  const list3 = (items) => items.length === 0 ? "_To be completed._" : items.map((i2) => `- ${i2}`).join("\n");
31269
31697
  return [
31270
- fm,
31698
+ fm2,
31271
31699
  `# ${data.system} \u2014 Knowledge Capsule
31272
31700
  `,
31273
31701
  `## Purpose
@@ -33867,7 +34295,7 @@ function renderUnderstand(plan) {
33867
34295
  }
33868
34296
 
33869
34297
  // ../cli/src/core/knowledge-consolidation.ts
33870
- import { createHash as createHash3 } from "crypto";
34298
+ import { createHash as createHash4 } from "crypto";
33871
34299
  import matter31 from "gray-matter";
33872
34300
  var KNOWLEDGE7 = "knowledge";
33873
34301
  var CONSOLIDATION_CATEGORIES = [
@@ -33887,7 +34315,7 @@ function relFromDir(dir, filePath) {
33887
34315
  return f3.startsWith(d3 + "/") ? f3.slice(d3.length + 1) : f3;
33888
34316
  }
33889
34317
  function sha256(content) {
33890
- return createHash3("sha256").update(content, "utf-8").digest("hex");
34318
+ return createHash4("sha256").update(content, "utf-8").digest("hex");
33891
34319
  }
33892
34320
  var SHINGLE_SIZE = 6;
33893
34321
  var MIN_SHARED_RUN = 6;
@@ -34739,6 +35167,14 @@ function completeInitiativeTool(root, args) {
34739
35167
  }
34740
35168
 
34741
35169
  // src/planning.ts
35170
+ var EVIDENCE_STATUSES = ["passed", "failed", "not-verified", "manual-review-required"];
35171
+ function toEvidence(a3) {
35172
+ if (!a3.criterion?.trim()) return "criterion is required.";
35173
+ const status = a3.status ?? "passed";
35174
+ if (!EVIDENCE_STATUSES.includes(status)) return `status must be one of: ${EVIDENCE_STATUSES.join(", ")}.`;
35175
+ const method = a3.method === "automated" || a3.method === "manual" ? a3.method : void 0;
35176
+ return { criterion: a3.criterion.trim(), status, evidence: a3.evidence, reference: a3.reference, method, verifiedBy: a3.verifiedBy };
35177
+ }
34742
35178
  var ok6 = (data) => ({ ok: true, data });
34743
35179
  var fail5 = (message) => ({ ok: false, message });
34744
35180
  function previewOrApply2(confirm2, action, effect, apply) {
@@ -34835,6 +35271,82 @@ function associateInitiativeTool(root, args) {
34835
35271
  () => associateInitiativeWithEpic(root, args.initiative, args.epic)
34836
35272
  );
34837
35273
  }
35274
+ function verifyPlanTool(root, id) {
35275
+ const plan = getPlan(root, id);
35276
+ if (!plan) return fail5(`Plan ${id} not found.`);
35277
+ return ok6(evaluatePlanCompletion(root, plan));
35278
+ }
35279
+ function verifyEpicTool(root, id) {
35280
+ const epic = getEpic(root, id);
35281
+ if (!epic) return fail5(`Epic ${id} not found.`);
35282
+ return ok6(evaluateEpicCompletion(root, epic));
35283
+ }
35284
+ function recordPlanEvidenceTool(root, args) {
35285
+ const input = toEvidence(args);
35286
+ if (typeof input === "string") return fail5(input);
35287
+ const target = args.milestone ? `milestone ${args.milestone}` : "Plan outcome";
35288
+ return previewOrApply2(
35289
+ args.confirm,
35290
+ "record-plan-evidence",
35291
+ `Evidence [${input.status}] for ${target} criterion "${input.criterion}" on ${args.id} will be recorded.`,
35292
+ () => args.milestone ? recordMilestoneExitVerification(root, args.id, args.milestone, input) : recordPlanOutcomeVerification(root, args.id, input)
35293
+ );
35294
+ }
35295
+ function recordEpicEvidenceTool(root, args) {
35296
+ const input = toEvidence(args);
35297
+ if (typeof input === "string") return fail5(input);
35298
+ return previewOrApply2(
35299
+ args.confirm,
35300
+ "record-epic-evidence",
35301
+ `Evidence [${input.status}] for Epic outcome criterion "${input.criterion}" on ${args.id} will be recorded.`,
35302
+ () => recordEpicOutcomeVerification(root, args.id, input)
35303
+ );
35304
+ }
35305
+ function transitionPlanTool(root, args) {
35306
+ if (args.to === "completed") return fail5("Use complete_plan for an evidence-based completion.");
35307
+ return previewOrApply2(
35308
+ args.confirm,
35309
+ "transition-plan",
35310
+ `Plan ${args.id} will transition to ${args.to}.`,
35311
+ () => transitionDeliveryPlan(root, args.id, args.to)
35312
+ );
35313
+ }
35314
+ function transitionEpicTool(root, args) {
35315
+ if (args.to === "completed") return fail5("Use complete_epic for an evidence-based completion.");
35316
+ return previewOrApply2(
35317
+ args.confirm,
35318
+ "transition-epic",
35319
+ `Epic ${args.id} will transition to ${args.to}.`,
35320
+ () => transitionEpic(root, args.id, args.to)
35321
+ );
35322
+ }
35323
+ var APPROVAL_NOTE = "confirm=true is a confirmation step, not by itself proof of human approval \u2014 only complete with genuine human authorization.";
35324
+ function completePlanTool(root, args) {
35325
+ const plan = getPlan(root, args.id);
35326
+ if (!plan) return fail5(`Plan ${args.id} not found.`);
35327
+ const evaluation = evaluatePlanCompletion(root, plan);
35328
+ const ready = evaluation.readiness === "READY_TO_COMPLETE" || evaluation.readiness === "READY_WITH_EXCEPTIONS";
35329
+ if (!args.confirm) return ok6({ action: "complete-plan", readiness: evaluation.readiness, evaluation, note: APPROVAL_NOTE, instruction: ready ? "Review the evaluation; call again with confirm=true to record the human completion decision." : `Not ready (${evaluation.readiness}). Resolve the blockers first.` });
35330
+ try {
35331
+ return ok6({ applied: true, result: completeDeliveryPlan(root, args.id, { decidedBy: args.decidedBy }) });
35332
+ } catch (err) {
35333
+ if (err instanceof PlanningNotReadyError || err instanceof DeliveryPlanningWriteError) return fail5(err.message);
35334
+ throw err;
35335
+ }
35336
+ }
35337
+ function completeEpicTool(root, args) {
35338
+ const epic = getEpic(root, args.id);
35339
+ if (!epic) return fail5(`Epic ${args.id} not found.`);
35340
+ const evaluation = evaluateEpicCompletion(root, epic);
35341
+ const ready = evaluation.readiness === "READY_TO_COMPLETE" || evaluation.readiness === "READY_WITH_EXCEPTIONS";
35342
+ if (!args.confirm) return ok6({ action: "complete-epic", readiness: evaluation.readiness, evaluation, note: APPROVAL_NOTE, instruction: ready ? "Review the evaluation; call again with confirm=true to record the human completion decision." : `Not ready (${evaluation.readiness}). Resolve the blockers first.` });
35343
+ try {
35344
+ return ok6({ applied: true, result: completeEpic(root, args.id, { decidedBy: args.decidedBy }) });
35345
+ } catch (err) {
35346
+ if (err instanceof PlanningNotReadyError || err instanceof DeliveryPlanningWriteError) return fail5(err.message);
35347
+ throw err;
35348
+ }
35349
+ }
34838
35350
 
34839
35351
  // src/collaboration.ts
34840
35352
  async function getWorkItemCollaborationTool(root, id) {
@@ -35487,6 +35999,47 @@ function createServer(root) {
35487
35999
  { 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() } },
35488
36000
  async (args) => toolText(guarded(root, () => associateInitiativeTool(root, args)))
35489
36001
  );
36002
+ const EVIDENCE_IN = { id: z4.string(), criterion: z4.string(), status: z4.string().optional(), evidence: z4.string().optional(), reference: z4.string().optional(), method: z4.string().optional(), verifiedBy: z4.string().optional(), confirm: z4.boolean().optional() };
36003
+ server.registerTool(
36004
+ "kaddo_verify_plan",
36005
+ { title: "Verify plan outcome", description: `Evidence-based completion readiness of a Plan: success criteria, milestone exit criteria, dependencies and reasons. ${PLANNING_READ_NOTE}`, inputSchema: { id: z4.string() } },
36006
+ async (args) => toolText(guarded(root, () => verifyPlanTool(root, args.id)))
36007
+ );
36008
+ server.registerTool(
36009
+ "kaddo_verify_epic",
36010
+ { title: "Verify epic outcome", description: `Evidence-based completion readiness of an Epic: success criteria, delivery, dependencies and reasons. ${PLANNING_READ_NOTE}`, inputSchema: { id: z4.string() } },
36011
+ async (args) => toolText(guarded(root, () => verifyEpicTool(root, args.id)))
36012
+ );
36013
+ server.registerTool(
36014
+ "kaddo_record_plan_evidence",
36015
+ { title: "Record plan evidence", description: `Record outcome (or milestone exit) verification evidence for one declared criterion. ${PLANNING_WRITE_NOTE}`, inputSchema: { ...EVIDENCE_IN, milestone: z4.string().optional() } },
36016
+ async (args) => toolText(guarded(root, () => recordPlanEvidenceTool(root, args)))
36017
+ );
36018
+ server.registerTool(
36019
+ "kaddo_record_epic_evidence",
36020
+ { title: "Record epic evidence", description: `Record outcome verification evidence for one declared success criterion. ${PLANNING_WRITE_NOTE}`, inputSchema: EVIDENCE_IN },
36021
+ async (args) => toolText(guarded(root, () => recordEpicEvidenceTool(root, args)))
36022
+ );
36023
+ server.registerTool(
36024
+ "kaddo_transition_plan",
36025
+ { title: "Transition plan", description: `Transition a Delivery Plan (start/cancel). Completion uses kaddo_complete_plan. ${PLANNING_WRITE_NOTE}`, inputSchema: { id: z4.string(), to: z4.string(), confirm: z4.boolean().optional() } },
36026
+ async (args) => toolText(guarded(root, () => transitionPlanTool(root, args)))
36027
+ );
36028
+ server.registerTool(
36029
+ "kaddo_transition_epic",
36030
+ { title: "Transition epic", description: `Transition an Epic (start/cancel). Completion uses kaddo_complete_epic. ${PLANNING_WRITE_NOTE}`, inputSchema: { id: z4.string(), to: z4.string(), confirm: z4.boolean().optional() } },
36031
+ async (args) => toolText(guarded(root, () => transitionEpicTool(root, args)))
36032
+ );
36033
+ server.registerTool(
36034
+ "kaddo_complete_plan",
36035
+ { title: "Complete plan", description: `Review readiness, then (confirm=true) record a human completion decision for a Plan. Never completes automatically; confirm is not itself proof of human approval. ${PLANNING_WRITE_NOTE}`, inputSchema: { id: z4.string(), decidedBy: z4.string().optional(), confirm: z4.boolean().optional() } },
36036
+ async (args) => toolText(guarded(root, () => completePlanTool(root, args)))
36037
+ );
36038
+ server.registerTool(
36039
+ "kaddo_complete_epic",
36040
+ { title: "Complete epic", description: `Review readiness, then (confirm=true) record a human completion decision for an Epic. Never completes automatically; confirm is not itself proof of human approval. ${PLANNING_WRITE_NOTE}`, inputSchema: { id: z4.string(), decidedBy: z4.string().optional(), confirm: z4.boolean().optional() } },
36041
+ async (args) => toolText(guarded(root, () => completeEpicTool(root, args)))
36042
+ );
35490
36043
  const INITIATIVE_WRITE_NOTE = "Without confirm=true returns a preview. With confirm=true applies the change. Writes only under knowledge/delivery/.";
35491
36044
  server.registerTool(
35492
36045
  "kaddo_create_initiative",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/mcp",
3
- "version": "3.134.0",
3
+ "version": "3.135.0",
4
4
  "description": "MCP server for Kaddo project knowledge: read-only resources/tools/prompts plus safe derived generation under .kaddo/.",
5
5
  "license": "MIT",
6
6
  "repository": {