@kaddo/mcp 3.105.0 → 3.107.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.
- package/dist/index.js +829 -17
- 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
|
|
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:
|
|
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,569 @@ 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
|
+
}
|
|
19058
|
+
var SUCCESS_HEADING_RE = /^#{1,6}\s*(success criteria|criterios de[ét]xito|criterios de éxito)\s*$/i;
|
|
19059
|
+
var HEADING_RE2 = /^#{1,6}\s+/;
|
|
19060
|
+
var CHECK_ITEM_RE = /^\s*[-*]\s+\[([ xX])\]\s+(.*)$/;
|
|
19061
|
+
var BULLET_RE2 = /^\s*[-*]\s+(.*)$/;
|
|
19062
|
+
var PLACEHOLDER_RE = /^_.*_$/;
|
|
19063
|
+
function parseSuccessCriteria(body) {
|
|
19064
|
+
const lines = body.split(/\r?\n/);
|
|
19065
|
+
const out = [];
|
|
19066
|
+
let capture = false;
|
|
19067
|
+
for (const line of lines) {
|
|
19068
|
+
if (capture && HEADING_RE2.test(line)) break;
|
|
19069
|
+
if (!capture) {
|
|
19070
|
+
if (SUCCESS_HEADING_RE.test(line.trim())) capture = true;
|
|
19071
|
+
continue;
|
|
19072
|
+
}
|
|
19073
|
+
const check = line.match(CHECK_ITEM_RE);
|
|
19074
|
+
if (check) {
|
|
19075
|
+
const text22 = check[2].trim();
|
|
19076
|
+
if (text22 && !PLACEHOLDER_RE.test(text22)) {
|
|
19077
|
+
out.push({ text: text22, checked: check[1].toLowerCase() === "x" });
|
|
19078
|
+
}
|
|
19079
|
+
continue;
|
|
19080
|
+
}
|
|
19081
|
+
const bullet = line.match(BULLET_RE2);
|
|
19082
|
+
if (bullet) {
|
|
19083
|
+
const text22 = bullet[1].trim();
|
|
19084
|
+
if (text22 && !PLACEHOLDER_RE.test(text22)) out.push({ text: text22, checked: null });
|
|
19085
|
+
}
|
|
19086
|
+
}
|
|
19087
|
+
return out;
|
|
19088
|
+
}
|
|
19089
|
+
function analyzeInitiative(dir, ini) {
|
|
19090
|
+
const progress = computeInitiativeProgress(dir, ini);
|
|
19091
|
+
const successCriteria = parseSuccessCriteria(ini.body);
|
|
19092
|
+
const pendingCandidates = ini.candidates.filter((c) => !c.materializedAs);
|
|
19093
|
+
const associated = associatedWorkItems(dir, ini.id);
|
|
19094
|
+
const findings = [];
|
|
19095
|
+
if (successCriteria.length === 0) {
|
|
19096
|
+
findings.push({
|
|
19097
|
+
code: "no-success-criteria",
|
|
19098
|
+
severity: "warning",
|
|
19099
|
+
message: "The Initiative has no explicit Success Criteria; completion cannot be judged by outcome, only by delivery."
|
|
19100
|
+
});
|
|
19101
|
+
} else {
|
|
19102
|
+
const uncovered = successCriteria.filter((c) => c.checked === false);
|
|
19103
|
+
if (uncovered.length > 0) {
|
|
19104
|
+
findings.push({
|
|
19105
|
+
code: "uncovered-success-criteria",
|
|
19106
|
+
severity: "blocking",
|
|
19107
|
+
message: `${uncovered.length} success criterion/criteria remain uncovered.`,
|
|
19108
|
+
items: uncovered.map((c) => c.text)
|
|
19109
|
+
});
|
|
19110
|
+
}
|
|
19111
|
+
}
|
|
19112
|
+
if (pendingCandidates.length > 0) {
|
|
19113
|
+
findings.push({
|
|
19114
|
+
code: "pending-candidates",
|
|
19115
|
+
severity: "blocking",
|
|
19116
|
+
message: `${pendingCandidates.length} committed candidate(s) not yet materialized into Work Items.`,
|
|
19117
|
+
items: pendingCandidates.map((c) => `${c.id}: ${c.title}`)
|
|
19118
|
+
});
|
|
19119
|
+
}
|
|
19120
|
+
const notCompleted = associated.filter((w3) => w3.status !== "completed" && w3.status !== "archived");
|
|
19121
|
+
if (associated.length === 0) {
|
|
19122
|
+
findings.push({
|
|
19123
|
+
code: "no-delivery",
|
|
19124
|
+
severity: "info",
|
|
19125
|
+
message: "No Work Items are associated with this Initiative yet."
|
|
19126
|
+
});
|
|
19127
|
+
} else if (notCompleted.length > 0) {
|
|
19128
|
+
findings.push({
|
|
19129
|
+
code: "incomplete-delivery",
|
|
19130
|
+
severity: "blocking",
|
|
19131
|
+
message: `${notCompleted.length} associated Work Item(s) are not completed.`,
|
|
19132
|
+
items: notCompleted.map((w3) => `${w3.id} (${w3.status})`)
|
|
19133
|
+
});
|
|
19134
|
+
}
|
|
19135
|
+
return {
|
|
19136
|
+
initiative: ini.id,
|
|
19137
|
+
status: ini.status,
|
|
19138
|
+
progress,
|
|
19139
|
+
successCriteria,
|
|
19140
|
+
pendingCandidates,
|
|
19141
|
+
findings,
|
|
19142
|
+
suggestedCandidates: pendingCandidates
|
|
19143
|
+
};
|
|
19144
|
+
}
|
|
19145
|
+
function evaluateInitiativeCompletion(dir, ini) {
|
|
19146
|
+
const analysis = analyzeInitiative(dir, ini);
|
|
19147
|
+
const reasons = [];
|
|
19148
|
+
for (const f3 of analysis.findings) {
|
|
19149
|
+
if (f3.severity === "blocking") reasons.push(f3.message);
|
|
19150
|
+
}
|
|
19151
|
+
if (analysis.progress.delivery.total === 0 && analysis.pendingCandidates.length === 0) {
|
|
19152
|
+
reasons.push("Nothing has been delivered or committed under this Initiative.");
|
|
19153
|
+
}
|
|
19154
|
+
const ready = reasons.length === 0;
|
|
19155
|
+
return {
|
|
19156
|
+
initiative: ini.id,
|
|
19157
|
+
ready,
|
|
19158
|
+
readiness: ready ? "ready" : "not-ready",
|
|
19159
|
+
reasons
|
|
19160
|
+
};
|
|
19161
|
+
}
|
|
18595
19162
|
|
|
18596
19163
|
// src/tools.ts
|
|
18597
19164
|
var ok = (data) => ({ ok: true, data });
|
|
@@ -18690,9 +19257,9 @@ function markWorkItemReady(root, id, confirm2) {
|
|
|
18690
19257
|
if (!raw) return fail(`Could not read Work Item file: ${item.path}`);
|
|
18691
19258
|
const parsed = matter12(raw);
|
|
18692
19259
|
const data = { ...parsed.data };
|
|
18693
|
-
const
|
|
19260
|
+
const today3 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
18694
19261
|
data.status = "ready";
|
|
18695
|
-
data.ready_at =
|
|
19262
|
+
data.ready_at = today3;
|
|
18696
19263
|
const newContent = matter12.stringify(parsed.content, data);
|
|
18697
19264
|
const posixPath = item.path.replace(/\\/g, "/");
|
|
18698
19265
|
let newRelPath = item.path;
|
|
@@ -18709,7 +19276,7 @@ function markWorkItemReady(root, id, confirm2) {
|
|
|
18709
19276
|
id: item.id,
|
|
18710
19277
|
from: "draft",
|
|
18711
19278
|
to: "ready",
|
|
18712
|
-
ready_at:
|
|
19279
|
+
ready_at: today3,
|
|
18713
19280
|
moved: newRelPath !== item.path ? { from: item.path, to: newRelPath } : void 0,
|
|
18714
19281
|
...warnings.length > 0 ? { warnings } : {},
|
|
18715
19282
|
next: ["Run kaddo context", "Run kaddo understand"]
|
|
@@ -18932,7 +19499,7 @@ function firstParagraph3(md) {
|
|
|
18932
19499
|
}
|
|
18933
19500
|
return "";
|
|
18934
19501
|
}
|
|
18935
|
-
function
|
|
19502
|
+
function slugify4(s) {
|
|
18936
19503
|
return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
18937
19504
|
}
|
|
18938
19505
|
function modulesListTool(root, args) {
|
|
@@ -19119,7 +19686,7 @@ function extractSection(content, heading) {
|
|
|
19119
19686
|
return out;
|
|
19120
19687
|
}
|
|
19121
19688
|
function buildBranchStrategy(wi, affectedModules, codeEntries, descriptor) {
|
|
19122
|
-
const slug5 =
|
|
19689
|
+
const slug5 = slugify4(wi.title);
|
|
19123
19690
|
const wiLower = wi.id.toLowerCase();
|
|
19124
19691
|
const coreBranch = `feature/${wiLower}-${slug5}`;
|
|
19125
19692
|
const moduleBranches = {};
|
|
@@ -19182,7 +19749,7 @@ function exportCapsuleTool(root, args) {
|
|
|
19182
19749
|
if (!mc) warnings.push("module-context.md not found for this module.");
|
|
19183
19750
|
if (mc && isPlaceholder2(mc)) warnings.push("module-context.md contains placeholder sections.");
|
|
19184
19751
|
capsuleType = "module";
|
|
19185
|
-
capsuleName = `${
|
|
19752
|
+
capsuleName = `${slugify4(config.project?.name ?? "project")}-${args.module}`;
|
|
19186
19753
|
capsuleData = {
|
|
19187
19754
|
type: "knowledge-capsule",
|
|
19188
19755
|
system: `${config.project?.name ?? "unknown"}/${args.module}`,
|
|
@@ -19204,7 +19771,7 @@ function exportCapsuleTool(root, args) {
|
|
|
19204
19771
|
if (mc && isPlaceholder2(mc)) warnings.push("module-context.md contains placeholder sections.");
|
|
19205
19772
|
const modId = config.module?.id ?? args.module;
|
|
19206
19773
|
capsuleType = "module";
|
|
19207
|
-
capsuleName =
|
|
19774
|
+
capsuleName = slugify4(modId);
|
|
19208
19775
|
capsuleData = {
|
|
19209
19776
|
type: "knowledge-capsule",
|
|
19210
19777
|
system: `${config.multirepo?.parent_system ?? "unknown"}/${modId}`,
|
|
@@ -19232,7 +19799,7 @@ function exportCapsuleTool(root, args) {
|
|
|
19232
19799
|
responsibilities.push(mc ? `[${m2.id}] ${firstParagraph3(mc) || m2.name || m2.id}` : `[${m2.id}] ${m2.name || m2.id} (no module-context)`);
|
|
19233
19800
|
}
|
|
19234
19801
|
capsuleType = "system";
|
|
19235
|
-
capsuleName = `${
|
|
19802
|
+
capsuleName = `${slugify4(config.project?.name ?? "project")}-system`;
|
|
19236
19803
|
capsuleData = {
|
|
19237
19804
|
type: "knowledge-capsule",
|
|
19238
19805
|
system: config.project?.name ?? "unknown",
|
|
@@ -19253,7 +19820,7 @@ function exportCapsuleTool(root, args) {
|
|
|
19253
19820
|
if (mc && isPlaceholder2(mc)) warnings.push("module-context.md contains placeholder sections.");
|
|
19254
19821
|
const modId = config.module?.id ?? "module";
|
|
19255
19822
|
capsuleType = "module";
|
|
19256
|
-
capsuleName =
|
|
19823
|
+
capsuleName = slugify4(modId);
|
|
19257
19824
|
capsuleData = {
|
|
19258
19825
|
type: "knowledge-capsule",
|
|
19259
19826
|
system: `${config.multirepo?.parent_system ?? "unknown"}/${modId}`,
|
|
@@ -19269,7 +19836,7 @@ function exportCapsuleTool(root, args) {
|
|
|
19269
19836
|
};
|
|
19270
19837
|
} else {
|
|
19271
19838
|
capsuleType = "project";
|
|
19272
|
-
capsuleName =
|
|
19839
|
+
capsuleName = slugify4(config.project?.name ?? "project");
|
|
19273
19840
|
capsuleData = {
|
|
19274
19841
|
type: "knowledge-capsule",
|
|
19275
19842
|
system: config.project?.name ?? "unknown",
|
|
@@ -19432,7 +19999,7 @@ var PROVENANCE2 = /* @__PURE__ */ new Set(["declared", "derived", "agent-reviewe
|
|
|
19432
19999
|
function isRelative2(p) {
|
|
19433
20000
|
return typeof p === "string" && p.trim() !== "" && !/^([a-zA-Z]:[\\/]|\/)/.test(p) && !p.includes("..");
|
|
19434
20001
|
}
|
|
19435
|
-
function
|
|
20002
|
+
function strList3(v) {
|
|
19436
20003
|
return Array.isArray(v) ? v.map((x2) => typeof x2 === "string" ? x2.trim() : "").filter(Boolean) : [];
|
|
19437
20004
|
}
|
|
19438
20005
|
function loadSystemTopology2(dir) {
|
|
@@ -19473,7 +20040,7 @@ function parseTopologyContent2(raw, dir) {
|
|
|
19473
20040
|
const moduleRaw = typeof e.module === "string" ? e.module.trim() : void 0;
|
|
19474
20041
|
if (moduleRaw && !validModules.has(moduleRaw)) findings.push({ level: "warning", message: `Entity "${id}" references unregistered module "${moduleRaw}".` });
|
|
19475
20042
|
const rawImpl = e.implementation ?? e.implementation_refs;
|
|
19476
|
-
const implementation =
|
|
20043
|
+
const implementation = strList3(rawImpl).filter((p) => {
|
|
19477
20044
|
if (isRelative2(p)) return true;
|
|
19478
20045
|
findings.push({ level: "warning", message: `Entity "${id}" implementation ref "${p}" is not a safe relative path and was dropped.` });
|
|
19479
20046
|
return false;
|
|
@@ -19481,7 +20048,7 @@ function parseTopologyContent2(raw, dir) {
|
|
|
19481
20048
|
const prov = e.provenance;
|
|
19482
20049
|
const provOrigin = typeof prov === "string" ? prov : prov && typeof prov === "object" && typeof prov.origin === "string" ? String(prov.origin) : void 0;
|
|
19483
20050
|
const provenance = provOrigin && PROVENANCE2.has(provOrigin) ? provOrigin : void 0;
|
|
19484
|
-
const evidence =
|
|
20051
|
+
const evidence = strList3(e.evidence ?? (prov && typeof prov === "object" ? prov.evidence_refs : void 0)).filter(isRelative2);
|
|
19485
20052
|
seenIds.add(id);
|
|
19486
20053
|
entities.push({
|
|
19487
20054
|
id,
|
|
@@ -19490,7 +20057,7 @@ function parseTopologyContent2(raw, dir) {
|
|
|
19490
20057
|
...typeof e.purpose === "string" && e.purpose.trim() ? { purpose: e.purpose.trim() } : {},
|
|
19491
20058
|
...moduleRaw && validModules.has(moduleRaw) ? { moduleId: moduleRaw } : {},
|
|
19492
20059
|
implementationRefs: implementation,
|
|
19493
|
-
knowledgeRefs:
|
|
20060
|
+
knowledgeRefs: strList3(e.knowledge ?? e.knowledge_refs),
|
|
19494
20061
|
...provenance ? { provenance } : {},
|
|
19495
20062
|
evidence
|
|
19496
20063
|
});
|
|
@@ -19524,7 +20091,7 @@ function parseTopologyContent2(raw, dir) {
|
|
|
19524
20091
|
}
|
|
19525
20092
|
seenRels.add(key);
|
|
19526
20093
|
const prov = r2.provenance;
|
|
19527
|
-
relationships.push({ from, to, type, evidence:
|
|
20094
|
+
relationships.push({ from, to, type, evidence: strList3(r2.evidence ?? prov?.evidence_refs).filter(isRelative2) });
|
|
19528
20095
|
}
|
|
19529
20096
|
return { entities, relationships, findings, ...sourceRevision ? { sourceRevision } : {} };
|
|
19530
20097
|
}
|
|
@@ -22094,6 +22661,147 @@ function generateCapsuleDraft(root) {
|
|
|
22094
22661
|
};
|
|
22095
22662
|
}
|
|
22096
22663
|
|
|
22664
|
+
// src/initiatives.ts
|
|
22665
|
+
var ok5 = (data) => ({ ok: true, data });
|
|
22666
|
+
var fail4 = (message) => ({ ok: false, message });
|
|
22667
|
+
function listInitiativesTool(root) {
|
|
22668
|
+
const all = discoverInitiatives(root).map((ini) => {
|
|
22669
|
+
const p = computeInitiativeProgress(root, ini);
|
|
22670
|
+
return {
|
|
22671
|
+
id: ini.id,
|
|
22672
|
+
title: ini.title,
|
|
22673
|
+
status: ini.status,
|
|
22674
|
+
planning: p.planning,
|
|
22675
|
+
delivery: p.delivery
|
|
22676
|
+
};
|
|
22677
|
+
});
|
|
22678
|
+
return ok5(all);
|
|
22679
|
+
}
|
|
22680
|
+
function getInitiativeTool(root, id) {
|
|
22681
|
+
const ini = getInitiative(root, id);
|
|
22682
|
+
if (!ini) return fail4(`Initiative ${id} not found.`);
|
|
22683
|
+
return ok5({
|
|
22684
|
+
id: ini.id,
|
|
22685
|
+
title: ini.title,
|
|
22686
|
+
status: ini.status,
|
|
22687
|
+
horizon: ini.horizon,
|
|
22688
|
+
priority: ini.priority,
|
|
22689
|
+
knowledgeLevel: ini.knowledgeLevel,
|
|
22690
|
+
domains: ini.domains,
|
|
22691
|
+
relatedCapabilities: ini.relatedCapabilities,
|
|
22692
|
+
source: ini.source,
|
|
22693
|
+
sourceId: ini.sourceId,
|
|
22694
|
+
externalLinks: ini.externalLinks,
|
|
22695
|
+
candidates: ini.candidates,
|
|
22696
|
+
body: ini.body
|
|
22697
|
+
});
|
|
22698
|
+
}
|
|
22699
|
+
function getInitiativeContextTool(root, id) {
|
|
22700
|
+
const ini = getInitiative(root, id);
|
|
22701
|
+
if (!ini) return fail4(`Initiative ${id} not found.`);
|
|
22702
|
+
return ok5(getInitiativeContext(root, ini));
|
|
22703
|
+
}
|
|
22704
|
+
function getInitiativeProgressTool(root, id) {
|
|
22705
|
+
const ini = getInitiative(root, id);
|
|
22706
|
+
if (!ini) return fail4(`Initiative ${id} not found.`);
|
|
22707
|
+
return ok5(computeInitiativeProgress(root, ini));
|
|
22708
|
+
}
|
|
22709
|
+
function previewOrApply(confirm2, action, effect, apply) {
|
|
22710
|
+
if (!confirm2) {
|
|
22711
|
+
return ok5({ action, effect, instruction: "Call again with confirm=true to apply." });
|
|
22712
|
+
}
|
|
22713
|
+
try {
|
|
22714
|
+
return ok5({ applied: true, result: apply() });
|
|
22715
|
+
} catch (err) {
|
|
22716
|
+
if (err instanceof InitiativeWriteError) return fail4(err.message);
|
|
22717
|
+
throw err;
|
|
22718
|
+
}
|
|
22719
|
+
}
|
|
22720
|
+
function createInitiativeTool(root, args) {
|
|
22721
|
+
if (!args.title?.trim()) return fail4("title is required.");
|
|
22722
|
+
return previewOrApply(
|
|
22723
|
+
args.confirm,
|
|
22724
|
+
"create-initiative",
|
|
22725
|
+
`A new Initiative "${args.title.trim()}" will be created under knowledge/delivery/initiatives/.`,
|
|
22726
|
+
() => createInitiative(root, {
|
|
22727
|
+
title: args.title,
|
|
22728
|
+
domains: args.domains,
|
|
22729
|
+
relatedCapabilities: args.relatedCapabilities,
|
|
22730
|
+
horizon: args.horizon,
|
|
22731
|
+
priority: args.priority
|
|
22732
|
+
})
|
|
22733
|
+
);
|
|
22734
|
+
}
|
|
22735
|
+
function updateInitiativeTool(root, args) {
|
|
22736
|
+
if (!getInitiative(root, args.id)) return fail4(`Initiative ${args.id} not found.`);
|
|
22737
|
+
const changingStatus = typeof args.status === "string";
|
|
22738
|
+
const effect = changingStatus ? `Initiative ${args.id} status will transition to "${args.status}" (valid transitions only).` : `Initiative ${args.id} fields will be updated.`;
|
|
22739
|
+
return previewOrApply(args.confirm, "update-initiative", effect, () => {
|
|
22740
|
+
if (changingStatus) {
|
|
22741
|
+
return transitionInitiative(root, args.id, args.status);
|
|
22742
|
+
}
|
|
22743
|
+
return updateInitiative(root, args.id, {
|
|
22744
|
+
title: args.title,
|
|
22745
|
+
horizon: args.horizon,
|
|
22746
|
+
priority: args.priority,
|
|
22747
|
+
domains: args.domains,
|
|
22748
|
+
relatedCapabilities: args.relatedCapabilities
|
|
22749
|
+
});
|
|
22750
|
+
});
|
|
22751
|
+
}
|
|
22752
|
+
function addInitiativeCandidateTool(root, args) {
|
|
22753
|
+
if (!args.title?.trim()) return fail4("candidate title is required.");
|
|
22754
|
+
if (!getInitiative(root, args.id)) return fail4(`Initiative ${args.id} not found.`);
|
|
22755
|
+
return previewOrApply(
|
|
22756
|
+
args.confirm,
|
|
22757
|
+
"add-initiative-candidate",
|
|
22758
|
+
`A Work Item candidate "${args.title.trim()}" will be added to ${args.id}.`,
|
|
22759
|
+
() => addCandidate(root, args.id, {
|
|
22760
|
+
title: args.title,
|
|
22761
|
+
type: args.type,
|
|
22762
|
+
suggestedKnowledgeLevel: args.suggestedKnowledgeLevel,
|
|
22763
|
+
expectedValue: args.expectedValue,
|
|
22764
|
+
notes: args.notes
|
|
22765
|
+
})
|
|
22766
|
+
);
|
|
22767
|
+
}
|
|
22768
|
+
function materializeInitiativeCandidateTool(root, args) {
|
|
22769
|
+
const ini = getInitiative(root, args.id);
|
|
22770
|
+
if (!ini) return fail4(`Initiative ${args.id} not found.`);
|
|
22771
|
+
const candidate = ini.candidates.find((c) => c.id === args.candidateId);
|
|
22772
|
+
if (!candidate) return fail4(`Candidate ${args.candidateId} not found in ${args.id}.`);
|
|
22773
|
+
return previewOrApply(
|
|
22774
|
+
args.confirm,
|
|
22775
|
+
"materialize-initiative-candidate",
|
|
22776
|
+
`Candidate ${args.candidateId} ("${candidate.title}") will be materialized into a draft Work Item associated to ${args.id}.`,
|
|
22777
|
+
() => materializeCandidate(root, args.id, args.candidateId)
|
|
22778
|
+
);
|
|
22779
|
+
}
|
|
22780
|
+
function analyzeInitiativeTool(root, id) {
|
|
22781
|
+
const ini = getInitiative(root, id);
|
|
22782
|
+
if (!ini) return fail4(`Initiative ${id} not found.`);
|
|
22783
|
+
return ok5(analyzeInitiative(root, ini));
|
|
22784
|
+
}
|
|
22785
|
+
function completeInitiativeTool(root, args) {
|
|
22786
|
+
const ini = getInitiative(root, args.id);
|
|
22787
|
+
if (!ini) return fail4(`Initiative ${args.id} not found.`);
|
|
22788
|
+
const readiness = evaluateInitiativeCompletion(root, ini);
|
|
22789
|
+
if (!args.confirm) {
|
|
22790
|
+
return ok5({
|
|
22791
|
+
action: "complete-initiative",
|
|
22792
|
+
readiness,
|
|
22793
|
+
instruction: readiness.ready ? "Initiative looks ready. Call again with confirm=true to complete (human decision)." : "Initiative is NOT ready (see readiness.reasons). Completion requires an explicit human decision: call again with confirm=true only if the human chooses to complete anyway."
|
|
22794
|
+
});
|
|
22795
|
+
}
|
|
22796
|
+
try {
|
|
22797
|
+
const done = transitionInitiative(root, args.id, "completed");
|
|
22798
|
+
return ok5({ applied: true, readiness, result: { id: done.id, status: done.status } });
|
|
22799
|
+
} catch (err) {
|
|
22800
|
+
if (err instanceof InitiativeWriteError) return fail4(err.message);
|
|
22801
|
+
throw err;
|
|
22802
|
+
}
|
|
22803
|
+
}
|
|
22804
|
+
|
|
22097
22805
|
// src/server.ts
|
|
22098
22806
|
var SERVER_NAME = "kaddo";
|
|
22099
22807
|
var require2 = createRequire(import.meta.url);
|
|
@@ -22490,6 +23198,110 @@ function createServer(root) {
|
|
|
22490
23198
|
},
|
|
22491
23199
|
async (args) => toolText(guarded(root, () => verifyWorkItemTool(root, args)))
|
|
22492
23200
|
);
|
|
23201
|
+
const INITIATIVE_READ_NOTE = "Read-only. Initiatives are an optional outcome/traceability layer over Work Items; a Work Item never requires one.";
|
|
23202
|
+
server.registerTool(
|
|
23203
|
+
"kaddo_list_initiatives",
|
|
23204
|
+
{ title: "List initiatives", description: `List initiatives with planning coverage and delivery progress. ${INITIATIVE_READ_NOTE}`, inputSchema: {} },
|
|
23205
|
+
async () => toolText(guarded(root, () => listInitiativesTool(root)))
|
|
23206
|
+
);
|
|
23207
|
+
server.registerTool(
|
|
23208
|
+
"kaddo_get_initiative",
|
|
23209
|
+
{ title: "Get initiative", description: `Get one initiative: metadata, candidates and body. ${INITIATIVE_READ_NOTE}`, inputSchema: { id: z4.string() } },
|
|
23210
|
+
async (args) => toolText(guarded(root, () => getInitiativeTool(root, args.id)))
|
|
23211
|
+
);
|
|
23212
|
+
server.registerTool(
|
|
23213
|
+
"kaddo_get_initiative_context",
|
|
23214
|
+
{
|
|
23215
|
+
title: "Get initiative context",
|
|
23216
|
+
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}`,
|
|
23217
|
+
inputSchema: { id: z4.string() }
|
|
23218
|
+
},
|
|
23219
|
+
async (args) => toolText(guarded(root, () => getInitiativeContextTool(root, args.id)))
|
|
23220
|
+
);
|
|
23221
|
+
server.registerTool(
|
|
23222
|
+
"kaddo_get_initiative_progress",
|
|
23223
|
+
{ 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() } },
|
|
23224
|
+
async (args) => toolText(guarded(root, () => getInitiativeProgressTool(root, args.id)))
|
|
23225
|
+
);
|
|
23226
|
+
const INITIATIVE_WRITE_NOTE = "Without confirm=true returns a preview. With confirm=true applies the change. Writes only under knowledge/delivery/.";
|
|
23227
|
+
server.registerTool(
|
|
23228
|
+
"kaddo_create_initiative",
|
|
23229
|
+
{
|
|
23230
|
+
title: "Create initiative",
|
|
23231
|
+
description: `Create a new initiative. ${INITIATIVE_WRITE_NOTE}`,
|
|
23232
|
+
inputSchema: {
|
|
23233
|
+
title: z4.string(),
|
|
23234
|
+
domains: z4.array(z4.string()).optional(),
|
|
23235
|
+
relatedCapabilities: z4.array(z4.string()).optional(),
|
|
23236
|
+
horizon: z4.string().optional(),
|
|
23237
|
+
priority: z4.string().optional(),
|
|
23238
|
+
confirm: z4.boolean().optional()
|
|
23239
|
+
}
|
|
23240
|
+
},
|
|
23241
|
+
async (args) => toolText(guarded(root, () => createInitiativeTool(root, args)))
|
|
23242
|
+
);
|
|
23243
|
+
server.registerTool(
|
|
23244
|
+
"kaddo_update_initiative",
|
|
23245
|
+
{
|
|
23246
|
+
title: "Update initiative",
|
|
23247
|
+
description: `Update an initiative's status (valid lifecycle transitions only) or non-lifecycle fields. ${INITIATIVE_WRITE_NOTE}`,
|
|
23248
|
+
inputSchema: {
|
|
23249
|
+
id: z4.string(),
|
|
23250
|
+
status: z4.enum(["planned", "in-progress", "completed", "deferred", "cancelled"]).optional(),
|
|
23251
|
+
title: z4.string().optional(),
|
|
23252
|
+
horizon: z4.string().optional(),
|
|
23253
|
+
priority: z4.string().optional(),
|
|
23254
|
+
domains: z4.array(z4.string()).optional(),
|
|
23255
|
+
relatedCapabilities: z4.array(z4.string()).optional(),
|
|
23256
|
+
confirm: z4.boolean().optional()
|
|
23257
|
+
}
|
|
23258
|
+
},
|
|
23259
|
+
async (args) => toolText(guarded(root, () => updateInitiativeTool(root, args)))
|
|
23260
|
+
);
|
|
23261
|
+
server.registerTool(
|
|
23262
|
+
"kaddo_add_initiative_candidate",
|
|
23263
|
+
{
|
|
23264
|
+
title: "Add initiative candidate",
|
|
23265
|
+
description: `Add a Work Item candidate to an initiative (not yet materialized). ${INITIATIVE_WRITE_NOTE}`,
|
|
23266
|
+
inputSchema: {
|
|
23267
|
+
id: z4.string(),
|
|
23268
|
+
title: z4.string(),
|
|
23269
|
+
type: z4.string().optional(),
|
|
23270
|
+
suggestedKnowledgeLevel: z4.string().optional(),
|
|
23271
|
+
expectedValue: z4.string().optional(),
|
|
23272
|
+
notes: z4.string().optional(),
|
|
23273
|
+
confirm: z4.boolean().optional()
|
|
23274
|
+
}
|
|
23275
|
+
},
|
|
23276
|
+
async (args) => toolText(guarded(root, () => addInitiativeCandidateTool(root, args)))
|
|
23277
|
+
);
|
|
23278
|
+
server.registerTool(
|
|
23279
|
+
"kaddo_materialize_initiative_candidate",
|
|
23280
|
+
{
|
|
23281
|
+
title: "Materialize initiative candidate",
|
|
23282
|
+
description: `Materialize an initiative candidate into a draft Work Item associated via \`initiative: INI-xxx\`. ${INITIATIVE_WRITE_NOTE}`,
|
|
23283
|
+
inputSchema: { id: z4.string(), candidateId: z4.string(), confirm: z4.boolean().optional() }
|
|
23284
|
+
},
|
|
23285
|
+
async (args) => toolText(guarded(root, () => materializeInitiativeCandidateTool(root, args)))
|
|
23286
|
+
);
|
|
23287
|
+
server.registerTool(
|
|
23288
|
+
"kaddo_analyze_initiative",
|
|
23289
|
+
{
|
|
23290
|
+
title: "Analyze initiative",
|
|
23291
|
+
description: `Read-only gap analysis: success-criteria coverage, pending candidates, delivery status, grounded findings, and the Initiative's own pending candidates as suggestions. Never invents work. ${INITIATIVE_READ_NOTE}`,
|
|
23292
|
+
inputSchema: { id: z4.string() }
|
|
23293
|
+
},
|
|
23294
|
+
async (args) => toolText(guarded(root, () => analyzeInitiativeTool(root, args.id)))
|
|
23295
|
+
);
|
|
23296
|
+
server.registerTool(
|
|
23297
|
+
"kaddo_complete_initiative",
|
|
23298
|
+
{
|
|
23299
|
+
title: "Complete initiative",
|
|
23300
|
+
description: "Human-gated completion. Without confirm returns completion readiness (does NOT complete). With confirm=true transitions the Initiative to completed; readiness findings are returned either way so uncovered scope stays visible. Never completes autonomously.",
|
|
23301
|
+
inputSchema: { id: z4.string(), confirm: z4.boolean().optional() }
|
|
23302
|
+
},
|
|
23303
|
+
async (args) => toolText(guarded(root, () => completeInitiativeTool(root, args)))
|
|
23304
|
+
);
|
|
22493
23305
|
server.registerTool(
|
|
22494
23306
|
"kaddo_telemetry_status",
|
|
22495
23307
|
{
|
package/package.json
CHANGED