@kaddo/mcp 3.105.0 → 3.106.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 -17
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -10828,6 +10828,9 @@ function writeFile2(filePath, content) {
10828
10828
  fs3.mkdirSync(path3.dirname(filePath), { recursive: true });
10829
10829
  fs3.writeFileSync(filePath, content, "utf-8");
10830
10830
  }
10831
+ function ensureDir2(dirPath) {
10832
+ fs3.mkdirSync(dirPath, { recursive: true });
10833
+ }
10831
10834
  function readDir2(dirPath) {
10832
10835
  if (!exists2(dirPath)) return [];
10833
10836
  return fs3.readdirSync(dirPath);
@@ -11071,6 +11074,9 @@ function lifecycleStateOf2(item) {
11071
11074
  if (folder) return folder;
11072
11075
  return "ready";
11073
11076
  }
11077
+ function emptyLifecycleCounts2() {
11078
+ return Object.fromEntries(LIFECYCLE_STATES2.map((s) => [s, 0]));
11079
+ }
11074
11080
  var ARCH_DIR3 = "knowledge";
11075
11081
  function toPosix6(p) {
11076
11082
  return p.replace(/\\/g, "/");
@@ -16967,7 +16973,7 @@ function createWorkItem(dir, opts) {
16967
16973
  if (!type) throw new WorkItemWriteError("INVALID_INPUT", `Unknown Work Item type "${opts.type}".`);
16968
16974
  const id = nextWorkItemId(dir);
16969
16975
  const title = intent.split(/\r?\n/)[0].trim().slice(0, 120);
16970
- const today2 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
16976
+ const today22 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
16971
16977
  const source = opts.source ? { ...opts.source, inferred: false } : { type: "manual", inferred: false };
16972
16978
  const data = {
16973
16979
  type,
@@ -16975,7 +16981,7 @@ function createWorkItem(dir, opts) {
16975
16981
  title,
16976
16982
  status: "draft",
16977
16983
  work_type: type,
16978
- created_at: today2,
16984
+ created_at: today22,
16979
16985
  source,
16980
16986
  generated_by: "kaddo-admin",
16981
16987
  affected_modules: [],
@@ -18581,6 +18587,7 @@ function getStatus(dir) {
18581
18587
  lastError: null
18582
18588
  };
18583
18589
  }
18590
+ var INITIATIVES_DIR = "knowledge/delivery/initiatives";
18584
18591
  var INITIATIVE_STATES = [
18585
18592
  "candidate",
18586
18593
  "planned",
@@ -18589,9 +18596,465 @@ var INITIATIVE_STATES = [
18589
18596
  "deferred",
18590
18597
  "cancelled"
18591
18598
  ];
18599
+ var INITIATIVE_TRANSITIONS = {
18600
+ candidate: ["planned", "cancelled"],
18601
+ planned: ["in-progress", "deferred", "cancelled"],
18602
+ "in-progress": ["completed", "deferred", "cancelled"],
18603
+ deferred: ["planned", "cancelled"],
18604
+ completed: [],
18605
+ cancelled: []
18606
+ };
18592
18607
  var STATUS_SET = new Set(INITIATIVE_STATES);
18608
+ function isValidInitiativeStatus(s) {
18609
+ return STATUS_SET.has(s);
18610
+ }
18611
+ function isValidInitiativeTransition(from, to) {
18612
+ return INITIATIVE_TRANSITIONS[from].includes(to);
18613
+ }
18614
+ function toPosix42(p) {
18615
+ return p.replace(/\\/g, "/");
18616
+ }
18617
+ function optStr32(v) {
18618
+ if (typeof v === "string" && v.trim()) return v.trim();
18619
+ if (typeof v === "number") return String(v);
18620
+ return void 0;
18621
+ }
18622
+ function strList2(v) {
18623
+ if (!Array.isArray(v)) return [];
18624
+ return v.map((x2) => typeof x2 === "string" ? x2.trim() : String(x2)).filter(Boolean);
18625
+ }
18626
+ function parseCandidates(data) {
18627
+ const raw = data.candidates;
18628
+ if (!Array.isArray(raw)) return [];
18629
+ const out = [];
18630
+ for (const entry of raw) {
18631
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
18632
+ const o2 = entry;
18633
+ const id = optStr32(o2.id);
18634
+ const title = optStr32(o2.title);
18635
+ if (!id || !title) continue;
18636
+ out.push({
18637
+ id,
18638
+ title,
18639
+ type: optStr32(o2.type),
18640
+ suggestedKnowledgeLevel: optStr32(o2.suggested_knowledge_level ?? o2.knowledge_level),
18641
+ expectedValue: optStr32(o2.expected_value),
18642
+ notes: optStr32(o2.notes),
18643
+ sourceSignals: strList2(o2.source_signals),
18644
+ dependencies: strList2(o2.dependencies),
18645
+ materializedAs: optStr32(o2.materialized_as) ?? null
18646
+ });
18647
+ }
18648
+ return out;
18649
+ }
18650
+ function parseExternalLinks(data) {
18651
+ const raw = data.external_links;
18652
+ if (!Array.isArray(raw)) return [];
18653
+ const out = [];
18654
+ for (const entry of raw) {
18655
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
18656
+ const o2 = entry;
18657
+ const integration = optStr32(o2.integration);
18658
+ const externalId = optStr32(o2.external_id);
18659
+ if (!integration || !externalId) continue;
18660
+ out.push({
18661
+ integration,
18662
+ externalId,
18663
+ externalType: optStr32(o2.external_type),
18664
+ url: optStr32(o2.url)
18665
+ });
18666
+ }
18667
+ return out;
18668
+ }
18669
+ function parseInitiative(dir, filePath, raw) {
18670
+ let parsed;
18671
+ try {
18672
+ parsed = matter102(raw, {});
18673
+ } catch {
18674
+ return null;
18675
+ }
18676
+ const data = parsed.data;
18677
+ if (optStr32(data.type) !== "initiative") return null;
18678
+ const id = optStr32(data.id);
18679
+ if (!id) return null;
18680
+ const statusRaw = optStr32(data.status) ?? "planned";
18681
+ const status = isValidInitiativeStatus(statusRaw) ? statusRaw : "planned";
18682
+ return {
18683
+ id,
18684
+ title: optStr32(data.title) ?? id,
18685
+ status,
18686
+ horizon: optStr32(data.horizon) ?? null,
18687
+ priority: optStr32(data.priority) ?? null,
18688
+ knowledgeLevel: optStr32(data.knowledge_level) ?? null,
18689
+ domains: strList2(data.domains),
18690
+ relatedCapabilities: strList2(data.related_capabilities),
18691
+ source: optStr32(data.source) ?? null,
18692
+ sourceId: optStr32(data.source_id) ?? null,
18693
+ createdAt: optStr32(data.created_at) ?? null,
18694
+ externalLinks: parseExternalLinks(data),
18695
+ candidates: parseCandidates(data),
18696
+ filePath,
18697
+ relPath: toPosix42(filePath).startsWith(toPosix42(dir) + "/") ? toPosix42(filePath).slice(toPosix42(dir).length + 1) : toPosix42(filePath),
18698
+ body: parsed.content.trim(),
18699
+ rawFrontmatter: data
18700
+ };
18701
+ }
18702
+ function discoverInitiatives(dir) {
18703
+ const iniDir = join3(dir, INITIATIVES_DIR);
18704
+ if (!exists2(iniDir)) return [];
18705
+ const out = [];
18706
+ for (const entry of readDir2(iniDir)) {
18707
+ if (!entry.endsWith(".md")) continue;
18708
+ const full = join3(iniDir, entry);
18709
+ if (!isFile2(full)) continue;
18710
+ const ini = parseInitiative(dir, full, readFile2(full));
18711
+ if (ini) out.push(ini);
18712
+ }
18713
+ return out.sort((a2, b2) => a2.id.localeCompare(b2.id));
18714
+ }
18715
+ function getInitiative(dir, id) {
18716
+ const want = id.trim().toLowerCase();
18717
+ return discoverInitiatives(dir).find((i) => i.id.toLowerCase() === want) ?? null;
18718
+ }
18719
+ function nextInitiativeId(dir) {
18720
+ const iniDir = join3(dir, INITIATIVES_DIR);
18721
+ let max = 0;
18722
+ if (exists2(iniDir)) {
18723
+ for (const entry of readDir2(iniDir)) {
18724
+ const m2 = entry.match(/INI-(\d+)/);
18725
+ if (m2) max = Math.max(max, parseInt(m2[1], 10));
18726
+ }
18727
+ }
18728
+ return `INI-${String(max + 1).padStart(3, "0")}`;
18729
+ }
18730
+ function workItemBelongsToInitiative(workItemInitiative, iniId) {
18731
+ const v = (workItemInitiative ?? "").trim().toLowerCase();
18732
+ return v !== "" && v === iniId.trim().toLowerCase();
18733
+ }
18734
+ function associatedWorkItems(dir, iniId) {
18735
+ return discoverWorkItems2(dir).filter((wi) => workItemBelongsToInitiative(wi.initiative, iniId)).map((wi) => ({
18736
+ id: wi.id,
18737
+ title: wi.title,
18738
+ status: wi.lifecycle ?? "ready"
18739
+ })).sort((a2, b2) => a2.id.localeCompare(b2.id));
18740
+ }
18741
+ function computeInitiativeProgress(dir, ini) {
18742
+ const totalCandidates = ini.candidates.length;
18743
+ const materialized = ini.candidates.filter((c) => c.materializedAs).length;
18744
+ const byState = emptyLifecycleCounts2();
18745
+ const associated = associatedWorkItems(dir, ini.id);
18746
+ for (const wi of associated) byState[wi.status]++;
18747
+ return {
18748
+ initiative: ini.id,
18749
+ status: ini.status,
18750
+ planning: {
18751
+ totalCandidates,
18752
+ materialized,
18753
+ remaining: Math.max(0, totalCandidates - materialized)
18754
+ },
18755
+ delivery: {
18756
+ total: associated.length,
18757
+ byState
18758
+ }
18759
+ };
18760
+ }
18761
+ function getInitiativeContext(dir, ini) {
18762
+ return {
18763
+ initiative: {
18764
+ id: ini.id,
18765
+ title: ini.title,
18766
+ status: ini.status,
18767
+ horizon: ini.horizon,
18768
+ priority: ini.priority,
18769
+ knowledgeLevel: ini.knowledgeLevel,
18770
+ domains: ini.domains,
18771
+ relatedCapabilities: ini.relatedCapabilities,
18772
+ source: ini.source,
18773
+ sourceId: ini.sourceId
18774
+ },
18775
+ progress: computeInitiativeProgress(dir, ini),
18776
+ candidates: {
18777
+ total: ini.candidates.length,
18778
+ materialized: ini.candidates.filter((c) => c.materializedAs),
18779
+ pending: ini.candidates.filter((c) => !c.materializedAs)
18780
+ },
18781
+ workItems: associatedWorkItems(dir, ini.id),
18782
+ externalLinks: ini.externalLinks
18783
+ };
18784
+ }
18593
18785
  var WORK_ITEMS_DIR2 = "knowledge/delivery/work-items";
18594
18786
  var DRAFT_DIR = `${WORK_ITEMS_DIR2}/draft`;
18787
+ var InitiativeWriteError = class extends Error {
18788
+ constructor(message) {
18789
+ super(message);
18790
+ this.name = "InitiativeWriteError";
18791
+ }
18792
+ };
18793
+ function slugify3(s) {
18794
+ return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 50);
18795
+ }
18796
+ function today2() {
18797
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
18798
+ }
18799
+ function candidateToData(c) {
18800
+ const out = { id: c.id, title: c.title };
18801
+ if (c.type) out.type = c.type;
18802
+ if (c.suggestedKnowledgeLevel) out.suggested_knowledge_level = c.suggestedKnowledgeLevel;
18803
+ if (c.expectedValue) out.expected_value = c.expectedValue;
18804
+ if (c.notes) out.notes = c.notes;
18805
+ if (c.sourceSignals.length) out.source_signals = c.sourceSignals;
18806
+ if (c.dependencies.length) out.dependencies = c.dependencies;
18807
+ if (c.materializedAs) out.materialized_as = c.materializedAs;
18808
+ return out;
18809
+ }
18810
+ function externalLinkToData(l2) {
18811
+ const out = { integration: l2.integration, external_id: l2.externalId };
18812
+ if (l2.externalType) out.external_type = l2.externalType;
18813
+ if (l2.url) out.url = l2.url;
18814
+ return out;
18815
+ }
18816
+ function normalizeCandidate(input, fallbackId) {
18817
+ return {
18818
+ id: input.id?.trim() || fallbackId,
18819
+ title: input.title.trim(),
18820
+ type: input.type?.trim() || void 0,
18821
+ suggestedKnowledgeLevel: input.suggestedKnowledgeLevel?.trim() || void 0,
18822
+ expectedValue: input.expectedValue?.trim() || void 0,
18823
+ notes: input.notes?.trim() || void 0,
18824
+ sourceSignals: input.sourceSignals ?? [],
18825
+ dependencies: input.dependencies ?? [],
18826
+ materializedAs: input.materializedAs ?? null
18827
+ };
18828
+ }
18829
+ function defaultBody(title) {
18830
+ return [
18831
+ `# ${title}`,
18832
+ "",
18833
+ "## Goal",
18834
+ "",
18835
+ "_What outcome does this Initiative pursue?_",
18836
+ "",
18837
+ "## Expected Value",
18838
+ "",
18839
+ "_Why it matters._",
18840
+ "",
18841
+ "## Scope",
18842
+ "",
18843
+ "_What this Initiative covers._",
18844
+ "",
18845
+ "## Out of Scope",
18846
+ "",
18847
+ "_What it explicitly does not cover._",
18848
+ "",
18849
+ "## Success Criteria",
18850
+ "",
18851
+ '_Observable outcomes that define completion (not just "all Work Items done")._',
18852
+ "",
18853
+ "## Dependencies",
18854
+ "",
18855
+ "_Other Initiatives, modules, or external work this depends on._",
18856
+ "",
18857
+ "## Work Item Candidates",
18858
+ "",
18859
+ "_Candidates live in frontmatter `candidates`; summarize them here as they evolve._",
18860
+ "",
18861
+ "## Open Questions",
18862
+ "",
18863
+ "_Unresolved questions for this Initiative._",
18864
+ "",
18865
+ "## Learning",
18866
+ "",
18867
+ "_Captured on completion: what was delivered, what stayed out of scope, outcome reached._",
18868
+ ""
18869
+ ].join("\n");
18870
+ }
18871
+ function buildInitiativeContent(id, input) {
18872
+ const status = input.status ?? "planned";
18873
+ if (!isValidInitiativeStatus(status)) {
18874
+ throw new InitiativeWriteError(`Invalid initiative status: ${status}.`);
18875
+ }
18876
+ const candidates = (input.candidates ?? []).map(
18877
+ (c, i) => normalizeCandidate(c, `WI-CANDIDATE-${String(i + 1).padStart(3, "0")}`)
18878
+ );
18879
+ const data = {
18880
+ type: "initiative",
18881
+ id,
18882
+ title: input.title.trim(),
18883
+ status,
18884
+ knowledge_level: input.knowledgeLevel ?? "K2",
18885
+ domains: input.domains ?? [],
18886
+ related_capabilities: input.relatedCapabilities ?? [],
18887
+ created_at: today2(),
18888
+ external_links: (input.externalLinks ?? []).map(externalLinkToData),
18889
+ candidates: candidates.map(candidateToData)
18890
+ };
18891
+ if (input.horizon) data.horizon = input.horizon;
18892
+ if (input.priority) data.priority = input.priority;
18893
+ if (input.source) data.source = input.source;
18894
+ if (input.sourceId) data.source_id = input.sourceId;
18895
+ return matter11.stringify("\n" + (input.body ?? defaultBody(input.title.trim())), data);
18896
+ }
18897
+ function createInitiative(dir, input) {
18898
+ if (!input.title?.trim()) throw new InitiativeWriteError("Initiative title is required.");
18899
+ const id = nextInitiativeId(dir);
18900
+ const fileName = `${id}-${slugify3(input.title)}.md`;
18901
+ const iniDir = join3(dir, INITIATIVES_DIR);
18902
+ ensureDir2(iniDir);
18903
+ const filePath = join3(iniDir, fileName);
18904
+ const content = buildInitiativeContent(id, input);
18905
+ writeFile2(filePath, content);
18906
+ const ini = parseInitiative(dir, filePath, content);
18907
+ if (!ini) throw new InitiativeWriteError("Failed to parse the Initiative just written.");
18908
+ return ini;
18909
+ }
18910
+ function transitionInitiative(dir, id, to) {
18911
+ const ini = getInitiative(dir, id);
18912
+ if (!ini) throw new InitiativeWriteError(`Initiative ${id} not found.`);
18913
+ if (!isValidInitiativeStatus(to)) throw new InitiativeWriteError(`Invalid status: ${to}.`);
18914
+ if (ini.status === to) return ini;
18915
+ if (!isValidInitiativeTransition(ini.status, to)) {
18916
+ throw new InitiativeWriteError(`Invalid transition ${ini.status} \u2192 ${to}.`);
18917
+ }
18918
+ const raw = readFile2(ini.filePath);
18919
+ const { data, content } = matter11(raw, {});
18920
+ data.status = to;
18921
+ if (to === "completed") data.completed_at = today2();
18922
+ const newContent = matter11.stringify(content, data);
18923
+ writeFile2(ini.filePath, newContent);
18924
+ const updated = parseInitiative(dir, ini.filePath, newContent);
18925
+ if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after transition.");
18926
+ return updated;
18927
+ }
18928
+ function addCandidate(dir, id, candidate) {
18929
+ const ini = getInitiative(dir, id);
18930
+ if (!ini) throw new InitiativeWriteError(`Initiative ${id} not found.`);
18931
+ const raw = readFile2(ini.filePath);
18932
+ const { data, content } = matter11(raw, {});
18933
+ const existing = Array.isArray(data.candidates) ? data.candidates : [];
18934
+ const fallbackId = `WI-CANDIDATE-${String(existing.length + 1).padStart(3, "0")}`;
18935
+ const normalized = normalizeCandidate(candidate, fallbackId);
18936
+ if (ini.candidates.some((c) => c.id === normalized.id)) {
18937
+ throw new InitiativeWriteError(`Candidate ${normalized.id} already exists in ${id}.`);
18938
+ }
18939
+ data.candidates = [...existing, candidateToData(normalized)];
18940
+ const newContent = matter11.stringify(content, data);
18941
+ writeFile2(ini.filePath, newContent);
18942
+ const updated = parseInitiative(dir, ini.filePath, newContent);
18943
+ if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after adding a candidate.");
18944
+ return updated;
18945
+ }
18946
+ function updateInitiative(dir, id, patch) {
18947
+ const ini = getInitiative(dir, id);
18948
+ if (!ini) throw new InitiativeWriteError(`Initiative ${id} not found.`);
18949
+ const raw = readFile2(ini.filePath);
18950
+ const { data, content } = matter11(raw, {});
18951
+ if (patch.title !== void 0) {
18952
+ if (!patch.title.trim()) throw new InitiativeWriteError("Initiative title cannot be empty.");
18953
+ data.title = patch.title.trim();
18954
+ }
18955
+ if (patch.horizon !== void 0) data.horizon = patch.horizon;
18956
+ if (patch.priority !== void 0) data.priority = patch.priority;
18957
+ if (patch.knowledgeLevel !== void 0) data.knowledge_level = patch.knowledgeLevel;
18958
+ if (patch.domains !== void 0) data.domains = patch.domains;
18959
+ if (patch.relatedCapabilities !== void 0) data.related_capabilities = patch.relatedCapabilities;
18960
+ const newContent = matter11.stringify(content, data);
18961
+ writeFile2(ini.filePath, newContent);
18962
+ const updated = parseInitiative(dir, ini.filePath, newContent);
18963
+ if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after update.");
18964
+ return updated;
18965
+ }
18966
+ function nextWorkItemId2(dir) {
18967
+ const wiDir = join3(dir, WORK_ITEMS_DIR2);
18968
+ let max = 0;
18969
+ const walk = (d3) => {
18970
+ if (!exists2(d3)) return;
18971
+ for (const entry of readDir2(d3)) {
18972
+ const full = join3(d3, entry);
18973
+ if (isFile2(full)) {
18974
+ const m2 = entry.match(/WI-(\d+)/);
18975
+ if (m2) max = Math.max(max, parseInt(m2[1], 10));
18976
+ } else if (!entry.startsWith(".")) {
18977
+ walk(full);
18978
+ }
18979
+ }
18980
+ };
18981
+ walk(wiDir);
18982
+ return `WI-${String(max + 1).padStart(3, "0")}`;
18983
+ }
18984
+ function buildWorkItemContent(wiId, iniId, candidate) {
18985
+ const data = {
18986
+ type: candidate.type ?? "feature",
18987
+ id: wiId,
18988
+ title: candidate.title,
18989
+ knowledge_level: candidate.suggestedKnowledgeLevel ?? "K2",
18990
+ status: "draft",
18991
+ phase: "now",
18992
+ initiative: iniId,
18993
+ domains: [],
18994
+ code: [],
18995
+ created_at: today2(),
18996
+ source: "initiative",
18997
+ source_id: candidate.id,
18998
+ source_initiative: iniId,
18999
+ affected_modules: []
19000
+ };
19001
+ if (candidate.sourceSignals.length) data.source_signals = candidate.sourceSignals;
19002
+ if (candidate.dependencies.length) data.dependencies = candidate.dependencies;
19003
+ const body = [
19004
+ `# ${candidate.title}`,
19005
+ "",
19006
+ `> Materialized from Initiative ${iniId}, candidate ${candidate.id}.`,
19007
+ "",
19008
+ "## Problem",
19009
+ "",
19010
+ candidate.expectedValue ? `_Expected value:_ ${candidate.expectedValue}` : "_Describe the problem._",
19011
+ "",
19012
+ "## Acceptance Criteria",
19013
+ "",
19014
+ "- [ ] _Define at least one testable criterion._",
19015
+ "",
19016
+ "## Out of scope",
19017
+ "",
19018
+ "_Not included in this Work Item._",
19019
+ "",
19020
+ "## Validation",
19021
+ "",
19022
+ "_How will this be validated?_",
19023
+ "",
19024
+ "## Learning",
19025
+ "",
19026
+ "_What did we learn? Update after completion._",
19027
+ ""
19028
+ ].join("\n");
19029
+ return matter11.stringify("\n" + body, data);
19030
+ }
19031
+ function materializeCandidate(dir, id, candidateId) {
19032
+ const ini = getInitiative(dir, id);
19033
+ if (!ini) throw new InitiativeWriteError(`Initiative ${id} not found.`);
19034
+ const candidate = ini.candidates.find((c) => c.id === candidateId);
19035
+ if (!candidate) throw new InitiativeWriteError(`Candidate ${candidateId} not found in ${id}.`);
19036
+ if (candidate.materializedAs) {
19037
+ throw new InitiativeWriteError(
19038
+ `Candidate ${candidateId} is already materialized as ${candidate.materializedAs}.`
19039
+ );
19040
+ }
19041
+ const wiId = nextWorkItemId2(dir);
19042
+ const fileName = `${wiId}-${slugify3(candidate.title)}.md`;
19043
+ ensureDir2(join3(dir, DRAFT_DIR));
19044
+ writeFile2(join3(dir, DRAFT_DIR, fileName), buildWorkItemContent(wiId, ini.id, candidate));
19045
+ const raw = readFile2(ini.filePath);
19046
+ const { data, content } = matter11(raw, {});
19047
+ const cands = Array.isArray(data.candidates) ? data.candidates : [];
19048
+ for (const c of cands) {
19049
+ if (c && typeof c === "object" && String(c.id) === candidateId) c.materialized_as = wiId;
19050
+ }
19051
+ data.candidates = cands;
19052
+ const newContent = matter11.stringify(content, data);
19053
+ writeFile2(ini.filePath, newContent);
19054
+ const updated = parseInitiative(dir, ini.filePath, newContent);
19055
+ if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after materialization.");
19056
+ return { workItemId: wiId, fileName, initiative: updated };
19057
+ }
18595
19058
 
18596
19059
  // src/tools.ts
18597
19060
  var ok = (data) => ({ ok: true, data });
@@ -18690,9 +19153,9 @@ function markWorkItemReady(root, id, confirm2) {
18690
19153
  if (!raw) return fail(`Could not read Work Item file: ${item.path}`);
18691
19154
  const parsed = matter12(raw);
18692
19155
  const data = { ...parsed.data };
18693
- const today2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
19156
+ const today3 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
18694
19157
  data.status = "ready";
18695
- data.ready_at = today2;
19158
+ data.ready_at = today3;
18696
19159
  const newContent = matter12.stringify(parsed.content, data);
18697
19160
  const posixPath = item.path.replace(/\\/g, "/");
18698
19161
  let newRelPath = item.path;
@@ -18709,7 +19172,7 @@ function markWorkItemReady(root, id, confirm2) {
18709
19172
  id: item.id,
18710
19173
  from: "draft",
18711
19174
  to: "ready",
18712
- ready_at: today2,
19175
+ ready_at: today3,
18713
19176
  moved: newRelPath !== item.path ? { from: item.path, to: newRelPath } : void 0,
18714
19177
  ...warnings.length > 0 ? { warnings } : {},
18715
19178
  next: ["Run kaddo context", "Run kaddo understand"]
@@ -18932,7 +19395,7 @@ function firstParagraph3(md) {
18932
19395
  }
18933
19396
  return "";
18934
19397
  }
18935
- function slugify3(s) {
19398
+ function slugify4(s) {
18936
19399
  return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
18937
19400
  }
18938
19401
  function modulesListTool(root, args) {
@@ -19119,7 +19582,7 @@ function extractSection(content, heading) {
19119
19582
  return out;
19120
19583
  }
19121
19584
  function buildBranchStrategy(wi, affectedModules, codeEntries, descriptor) {
19122
- const slug5 = slugify3(wi.title);
19585
+ const slug5 = slugify4(wi.title);
19123
19586
  const wiLower = wi.id.toLowerCase();
19124
19587
  const coreBranch = `feature/${wiLower}-${slug5}`;
19125
19588
  const moduleBranches = {};
@@ -19182,7 +19645,7 @@ function exportCapsuleTool(root, args) {
19182
19645
  if (!mc) warnings.push("module-context.md not found for this module.");
19183
19646
  if (mc && isPlaceholder2(mc)) warnings.push("module-context.md contains placeholder sections.");
19184
19647
  capsuleType = "module";
19185
- capsuleName = `${slugify3(config.project?.name ?? "project")}-${args.module}`;
19648
+ capsuleName = `${slugify4(config.project?.name ?? "project")}-${args.module}`;
19186
19649
  capsuleData = {
19187
19650
  type: "knowledge-capsule",
19188
19651
  system: `${config.project?.name ?? "unknown"}/${args.module}`,
@@ -19204,7 +19667,7 @@ function exportCapsuleTool(root, args) {
19204
19667
  if (mc && isPlaceholder2(mc)) warnings.push("module-context.md contains placeholder sections.");
19205
19668
  const modId = config.module?.id ?? args.module;
19206
19669
  capsuleType = "module";
19207
- capsuleName = slugify3(modId);
19670
+ capsuleName = slugify4(modId);
19208
19671
  capsuleData = {
19209
19672
  type: "knowledge-capsule",
19210
19673
  system: `${config.multirepo?.parent_system ?? "unknown"}/${modId}`,
@@ -19232,7 +19695,7 @@ function exportCapsuleTool(root, args) {
19232
19695
  responsibilities.push(mc ? `[${m2.id}] ${firstParagraph3(mc) || m2.name || m2.id}` : `[${m2.id}] ${m2.name || m2.id} (no module-context)`);
19233
19696
  }
19234
19697
  capsuleType = "system";
19235
- capsuleName = `${slugify3(config.project?.name ?? "project")}-system`;
19698
+ capsuleName = `${slugify4(config.project?.name ?? "project")}-system`;
19236
19699
  capsuleData = {
19237
19700
  type: "knowledge-capsule",
19238
19701
  system: config.project?.name ?? "unknown",
@@ -19253,7 +19716,7 @@ function exportCapsuleTool(root, args) {
19253
19716
  if (mc && isPlaceholder2(mc)) warnings.push("module-context.md contains placeholder sections.");
19254
19717
  const modId = config.module?.id ?? "module";
19255
19718
  capsuleType = "module";
19256
- capsuleName = slugify3(modId);
19719
+ capsuleName = slugify4(modId);
19257
19720
  capsuleData = {
19258
19721
  type: "knowledge-capsule",
19259
19722
  system: `${config.multirepo?.parent_system ?? "unknown"}/${modId}`,
@@ -19269,7 +19732,7 @@ function exportCapsuleTool(root, args) {
19269
19732
  };
19270
19733
  } else {
19271
19734
  capsuleType = "project";
19272
- capsuleName = slugify3(config.project?.name ?? "project");
19735
+ capsuleName = slugify4(config.project?.name ?? "project");
19273
19736
  capsuleData = {
19274
19737
  type: "knowledge-capsule",
19275
19738
  system: config.project?.name ?? "unknown",
@@ -19432,7 +19895,7 @@ var PROVENANCE2 = /* @__PURE__ */ new Set(["declared", "derived", "agent-reviewe
19432
19895
  function isRelative2(p) {
19433
19896
  return typeof p === "string" && p.trim() !== "" && !/^([a-zA-Z]:[\\/]|\/)/.test(p) && !p.includes("..");
19434
19897
  }
19435
- function strList2(v) {
19898
+ function strList3(v) {
19436
19899
  return Array.isArray(v) ? v.map((x2) => typeof x2 === "string" ? x2.trim() : "").filter(Boolean) : [];
19437
19900
  }
19438
19901
  function loadSystemTopology2(dir) {
@@ -19473,7 +19936,7 @@ function parseTopologyContent2(raw, dir) {
19473
19936
  const moduleRaw = typeof e.module === "string" ? e.module.trim() : void 0;
19474
19937
  if (moduleRaw && !validModules.has(moduleRaw)) findings.push({ level: "warning", message: `Entity "${id}" references unregistered module "${moduleRaw}".` });
19475
19938
  const rawImpl = e.implementation ?? e.implementation_refs;
19476
- const implementation = strList2(rawImpl).filter((p) => {
19939
+ const implementation = strList3(rawImpl).filter((p) => {
19477
19940
  if (isRelative2(p)) return true;
19478
19941
  findings.push({ level: "warning", message: `Entity "${id}" implementation ref "${p}" is not a safe relative path and was dropped.` });
19479
19942
  return false;
@@ -19481,7 +19944,7 @@ function parseTopologyContent2(raw, dir) {
19481
19944
  const prov = e.provenance;
19482
19945
  const provOrigin = typeof prov === "string" ? prov : prov && typeof prov === "object" && typeof prov.origin === "string" ? String(prov.origin) : void 0;
19483
19946
  const provenance = provOrigin && PROVENANCE2.has(provOrigin) ? provOrigin : void 0;
19484
- const evidence = strList2(e.evidence ?? (prov && typeof prov === "object" ? prov.evidence_refs : void 0)).filter(isRelative2);
19947
+ const evidence = strList3(e.evidence ?? (prov && typeof prov === "object" ? prov.evidence_refs : void 0)).filter(isRelative2);
19485
19948
  seenIds.add(id);
19486
19949
  entities.push({
19487
19950
  id,
@@ -19490,7 +19953,7 @@ function parseTopologyContent2(raw, dir) {
19490
19953
  ...typeof e.purpose === "string" && e.purpose.trim() ? { purpose: e.purpose.trim() } : {},
19491
19954
  ...moduleRaw && validModules.has(moduleRaw) ? { moduleId: moduleRaw } : {},
19492
19955
  implementationRefs: implementation,
19493
- knowledgeRefs: strList2(e.knowledge ?? e.knowledge_refs),
19956
+ knowledgeRefs: strList3(e.knowledge ?? e.knowledge_refs),
19494
19957
  ...provenance ? { provenance } : {},
19495
19958
  evidence
19496
19959
  });
@@ -19524,7 +19987,7 @@ function parseTopologyContent2(raw, dir) {
19524
19987
  }
19525
19988
  seenRels.add(key);
19526
19989
  const prov = r2.provenance;
19527
- relationships.push({ from, to, type, evidence: strList2(r2.evidence ?? prov?.evidence_refs).filter(isRelative2) });
19990
+ relationships.push({ from, to, type, evidence: strList3(r2.evidence ?? prov?.evidence_refs).filter(isRelative2) });
19528
19991
  }
19529
19992
  return { entities, relationships, findings, ...sourceRevision ? { sourceRevision } : {} };
19530
19993
  }
@@ -22094,6 +22557,123 @@ function generateCapsuleDraft(root) {
22094
22557
  };
22095
22558
  }
22096
22559
 
22560
+ // src/initiatives.ts
22561
+ var ok5 = (data) => ({ ok: true, data });
22562
+ var fail4 = (message) => ({ ok: false, message });
22563
+ function listInitiativesTool(root) {
22564
+ const all = discoverInitiatives(root).map((ini) => {
22565
+ const p = computeInitiativeProgress(root, ini);
22566
+ return {
22567
+ id: ini.id,
22568
+ title: ini.title,
22569
+ status: ini.status,
22570
+ planning: p.planning,
22571
+ delivery: p.delivery
22572
+ };
22573
+ });
22574
+ return ok5(all);
22575
+ }
22576
+ function getInitiativeTool(root, id) {
22577
+ const ini = getInitiative(root, id);
22578
+ if (!ini) return fail4(`Initiative ${id} not found.`);
22579
+ return ok5({
22580
+ id: ini.id,
22581
+ title: ini.title,
22582
+ status: ini.status,
22583
+ horizon: ini.horizon,
22584
+ priority: ini.priority,
22585
+ knowledgeLevel: ini.knowledgeLevel,
22586
+ domains: ini.domains,
22587
+ relatedCapabilities: ini.relatedCapabilities,
22588
+ source: ini.source,
22589
+ sourceId: ini.sourceId,
22590
+ externalLinks: ini.externalLinks,
22591
+ candidates: ini.candidates,
22592
+ body: ini.body
22593
+ });
22594
+ }
22595
+ function getInitiativeContextTool(root, id) {
22596
+ const ini = getInitiative(root, id);
22597
+ if (!ini) return fail4(`Initiative ${id} not found.`);
22598
+ return ok5(getInitiativeContext(root, ini));
22599
+ }
22600
+ function getInitiativeProgressTool(root, id) {
22601
+ const ini = getInitiative(root, id);
22602
+ if (!ini) return fail4(`Initiative ${id} not found.`);
22603
+ return ok5(computeInitiativeProgress(root, ini));
22604
+ }
22605
+ function previewOrApply(confirm2, action, effect, apply) {
22606
+ if (!confirm2) {
22607
+ return ok5({ action, effect, instruction: "Call again with confirm=true to apply." });
22608
+ }
22609
+ try {
22610
+ return ok5({ applied: true, result: apply() });
22611
+ } catch (err) {
22612
+ if (err instanceof InitiativeWriteError) return fail4(err.message);
22613
+ throw err;
22614
+ }
22615
+ }
22616
+ function createInitiativeTool(root, args) {
22617
+ if (!args.title?.trim()) return fail4("title is required.");
22618
+ return previewOrApply(
22619
+ args.confirm,
22620
+ "create-initiative",
22621
+ `A new Initiative "${args.title.trim()}" will be created under knowledge/delivery/initiatives/.`,
22622
+ () => createInitiative(root, {
22623
+ title: args.title,
22624
+ domains: args.domains,
22625
+ relatedCapabilities: args.relatedCapabilities,
22626
+ horizon: args.horizon,
22627
+ priority: args.priority
22628
+ })
22629
+ );
22630
+ }
22631
+ function updateInitiativeTool(root, args) {
22632
+ if (!getInitiative(root, args.id)) return fail4(`Initiative ${args.id} not found.`);
22633
+ const changingStatus = typeof args.status === "string";
22634
+ const effect = changingStatus ? `Initiative ${args.id} status will transition to "${args.status}" (valid transitions only).` : `Initiative ${args.id} fields will be updated.`;
22635
+ return previewOrApply(args.confirm, "update-initiative", effect, () => {
22636
+ if (changingStatus) {
22637
+ return transitionInitiative(root, args.id, args.status);
22638
+ }
22639
+ return updateInitiative(root, args.id, {
22640
+ title: args.title,
22641
+ horizon: args.horizon,
22642
+ priority: args.priority,
22643
+ domains: args.domains,
22644
+ relatedCapabilities: args.relatedCapabilities
22645
+ });
22646
+ });
22647
+ }
22648
+ function addInitiativeCandidateTool(root, args) {
22649
+ if (!args.title?.trim()) return fail4("candidate title is required.");
22650
+ if (!getInitiative(root, args.id)) return fail4(`Initiative ${args.id} not found.`);
22651
+ return previewOrApply(
22652
+ args.confirm,
22653
+ "add-initiative-candidate",
22654
+ `A Work Item candidate "${args.title.trim()}" will be added to ${args.id}.`,
22655
+ () => addCandidate(root, args.id, {
22656
+ title: args.title,
22657
+ type: args.type,
22658
+ suggestedKnowledgeLevel: args.suggestedKnowledgeLevel,
22659
+ expectedValue: args.expectedValue,
22660
+ notes: args.notes
22661
+ })
22662
+ );
22663
+ }
22664
+ function materializeInitiativeCandidateTool(root, args) {
22665
+ const ini = getInitiative(root, args.id);
22666
+ if (!ini) return fail4(`Initiative ${args.id} not found.`);
22667
+ const candidate = ini.candidates.find((c) => c.id === args.candidateId);
22668
+ if (!candidate) return fail4(`Candidate ${args.candidateId} not found in ${args.id}.`);
22669
+ return previewOrApply(
22670
+ args.confirm,
22671
+ "materialize-initiative-candidate",
22672
+ `Candidate ${args.candidateId} ("${candidate.title}") will be materialized into a draft Work Item associated to ${args.id}.`,
22673
+ () => materializeCandidate(root, args.id, args.candidateId)
22674
+ );
22675
+ }
22676
+
22097
22677
  // src/server.ts
22098
22678
  var SERVER_NAME = "kaddo";
22099
22679
  var require2 = createRequire(import.meta.url);
@@ -22490,6 +23070,92 @@ function createServer(root) {
22490
23070
  },
22491
23071
  async (args) => toolText(guarded(root, () => verifyWorkItemTool(root, args)))
22492
23072
  );
23073
+ const INITIATIVE_READ_NOTE = "Read-only. Initiatives are an optional outcome/traceability layer over Work Items; a Work Item never requires one.";
23074
+ server.registerTool(
23075
+ "kaddo_list_initiatives",
23076
+ { title: "List initiatives", description: `List initiatives with planning coverage and delivery progress. ${INITIATIVE_READ_NOTE}`, inputSchema: {} },
23077
+ async () => toolText(guarded(root, () => listInitiativesTool(root)))
23078
+ );
23079
+ server.registerTool(
23080
+ "kaddo_get_initiative",
23081
+ { title: "Get initiative", description: `Get one initiative: metadata, candidates and body. ${INITIATIVE_READ_NOTE}`, inputSchema: { id: z4.string() } },
23082
+ async (args) => toolText(guarded(root, () => getInitiativeTool(root, args.id)))
23083
+ );
23084
+ server.registerTool(
23085
+ "kaddo_get_initiative_context",
23086
+ {
23087
+ title: "Get initiative context",
23088
+ description: `Focused, initiative-scoped context for analysis: the initiative, progress, candidates (materialized/pending), associated Work Items and external links. Does NOT load the whole project. ${INITIATIVE_READ_NOTE}`,
23089
+ inputSchema: { id: z4.string() }
23090
+ },
23091
+ async (args) => toolText(guarded(root, () => getInitiativeContextTool(root, args.id)))
23092
+ );
23093
+ server.registerTool(
23094
+ "kaddo_get_initiative_progress",
23095
+ { 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() } },
23096
+ async (args) => toolText(guarded(root, () => getInitiativeProgressTool(root, args.id)))
23097
+ );
23098
+ const INITIATIVE_WRITE_NOTE = "Without confirm=true returns a preview. With confirm=true applies the change. Writes only under knowledge/delivery/.";
23099
+ server.registerTool(
23100
+ "kaddo_create_initiative",
23101
+ {
23102
+ title: "Create initiative",
23103
+ description: `Create a new initiative. ${INITIATIVE_WRITE_NOTE}`,
23104
+ inputSchema: {
23105
+ title: z4.string(),
23106
+ domains: z4.array(z4.string()).optional(),
23107
+ relatedCapabilities: z4.array(z4.string()).optional(),
23108
+ horizon: z4.string().optional(),
23109
+ priority: z4.string().optional(),
23110
+ confirm: z4.boolean().optional()
23111
+ }
23112
+ },
23113
+ async (args) => toolText(guarded(root, () => createInitiativeTool(root, args)))
23114
+ );
23115
+ server.registerTool(
23116
+ "kaddo_update_initiative",
23117
+ {
23118
+ title: "Update initiative",
23119
+ description: `Update an initiative's status (valid lifecycle transitions only) or non-lifecycle fields. ${INITIATIVE_WRITE_NOTE}`,
23120
+ inputSchema: {
23121
+ id: z4.string(),
23122
+ status: z4.enum(["planned", "in-progress", "completed", "deferred", "cancelled"]).optional(),
23123
+ title: z4.string().optional(),
23124
+ horizon: z4.string().optional(),
23125
+ priority: z4.string().optional(),
23126
+ domains: z4.array(z4.string()).optional(),
23127
+ relatedCapabilities: z4.array(z4.string()).optional(),
23128
+ confirm: z4.boolean().optional()
23129
+ }
23130
+ },
23131
+ async (args) => toolText(guarded(root, () => updateInitiativeTool(root, args)))
23132
+ );
23133
+ server.registerTool(
23134
+ "kaddo_add_initiative_candidate",
23135
+ {
23136
+ title: "Add initiative candidate",
23137
+ description: `Add a Work Item candidate to an initiative (not yet materialized). ${INITIATIVE_WRITE_NOTE}`,
23138
+ inputSchema: {
23139
+ id: z4.string(),
23140
+ title: z4.string(),
23141
+ type: z4.string().optional(),
23142
+ suggestedKnowledgeLevel: z4.string().optional(),
23143
+ expectedValue: z4.string().optional(),
23144
+ notes: z4.string().optional(),
23145
+ confirm: z4.boolean().optional()
23146
+ }
23147
+ },
23148
+ async (args) => toolText(guarded(root, () => addInitiativeCandidateTool(root, args)))
23149
+ );
23150
+ server.registerTool(
23151
+ "kaddo_materialize_initiative_candidate",
23152
+ {
23153
+ title: "Materialize initiative candidate",
23154
+ description: `Materialize an initiative candidate into a draft Work Item associated via \`initiative: INI-xxx\`. ${INITIATIVE_WRITE_NOTE}`,
23155
+ inputSchema: { id: z4.string(), candidateId: z4.string(), confirm: z4.boolean().optional() }
23156
+ },
23157
+ async (args) => toolText(guarded(root, () => materializeInitiativeCandidateTool(root, args)))
23158
+ );
22493
23159
  server.registerTool(
22494
23160
  "kaddo_telemetry_status",
22495
23161
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/mcp",
3
- "version": "3.105.0",
3
+ "version": "3.106.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": {