@kaddo/mcp 3.61.0 → 3.62.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 +548 -19
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -475,8 +475,8 @@ function loadConfig(dir) {
475
475
  const parsed = configSchema.safeParse(raw ?? {});
476
476
  if (!parsed.success) {
477
477
  const issues = parsed.error.issues.map((i) => {
478
- const path3 = i.path.join(".");
479
- return path3 ? ` - ${path3}: ${i.message}` : ` - ${i.message}`;
478
+ const path4 = i.path.join(".");
479
+ return path4 ? ` - ${path4}: ${i.message}` : ` - ${i.message}`;
480
480
  }).join("\n");
481
481
  throw new ConfigError(`Invalid .kaddo/config.yml:
482
482
  ${issues}`);
@@ -829,11 +829,11 @@ function moduleArtifactCoverage(dir, id) {
829
829
  };
830
830
  }
831
831
  function loadMappedModules(dir) {
832
- const path3 = join(dir, DESCRIPTOR_PATH);
833
- if (!exists(path3)) return [];
832
+ const path4 = join(dir, DESCRIPTOR_PATH);
833
+ if (!exists(path4)) return [];
834
834
  let parsed;
835
835
  try {
836
- parsed = parseYaml4(readFile(path3));
836
+ parsed = parseYaml4(readFile(path4));
837
837
  } catch {
838
838
  return [];
839
839
  }
@@ -2283,7 +2283,7 @@ function sectionParagraph2(md, title) {
2283
2283
  }
2284
2284
  return "";
2285
2285
  }
2286
- function parseCapsule(id, path3, md) {
2286
+ function parseCapsule(id, path4, md) {
2287
2287
  const { data } = matter4(md);
2288
2288
  const updatedAt = data.updated_at ? String(data.updated_at) : void 0;
2289
2289
  let ageDays = null;
@@ -2293,7 +2293,7 @@ function parseCapsule(id, path3, md) {
2293
2293
  }
2294
2294
  return {
2295
2295
  id,
2296
- path: path3,
2296
+ path: path4,
2297
2297
  system: data.system ? String(data.system) : id,
2298
2298
  owner: data.owner ? String(data.owner) : void 0,
2299
2299
  updatedAt,
@@ -2355,9 +2355,9 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
2355
2355
  ];
2356
2356
  const presentLayers = [];
2357
2357
  for (const layer2 of layerDocs) {
2358
- const path3 = layer2.files.map((f3) => `${KNOWLEDGE3}/${f3}`).find((rel) => exists(join(dir, rel)));
2359
- if (path3) {
2360
- addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path: path3 });
2358
+ const path4 = layer2.files.map((f3) => `${KNOWLEDGE3}/${f3}`).find((rel) => exists(join(dir, rel)));
2359
+ if (path4) {
2360
+ addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path: path4 });
2361
2361
  presentLayers.push(layer2.id);
2362
2362
  }
2363
2363
  }
@@ -3834,6 +3834,7 @@ function buildDeliveryState(dir) {
3834
3834
  refined_draft_ids: wis.filter((w3) => w3.lifecycle === "draft" && w3.rawFrontmatter.refined_by).map((w3) => w3.id),
3835
3835
  ready_work_items: byState("ready"),
3836
3836
  ready_work_item_ids: wis.filter((w3) => w3.lifecycle === "ready").map((w3) => w3.id),
3837
+ ready_multirepo_wi_ids: wis.filter((w3) => w3.lifecycle === "ready" && Array.isArray(w3.rawFrontmatter?.affected_modules) && w3.rawFrontmatter.affected_modules.length > 0).map((w3) => w3.id),
3837
3838
  in_progress_work_items: byState("in-progress"),
3838
3839
  blocked_work_items: byState("blocked"),
3839
3840
  total_work_items: total,
@@ -3966,10 +3967,10 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
3966
3967
  const discovery = state === "pre-ai" || state === "legacy";
3967
3968
  const resolveAgent = (agent) => {
3968
3969
  const file = agent.endsWith(".md") ? agent : `${agent}.md`;
3969
- const path3 = agentInstallPath(file);
3970
- const installed = isFile(join(dir, path3));
3970
+ const path4 = agentInstallPath(file);
3971
+ const installed = isFile(join(dir, path4));
3971
3972
  const group = agentGroupOf(file);
3972
- return { agentPath: path3, agentInstalled: installed, installCommand: installed ? void 0 : `kaddo add agents --group ${group}` };
3973
+ return { agentPath: path4, agentInstalled: installed, installCommand: installed ? void 0 : `kaddo add agents --group ${group}` };
3973
3974
  };
3974
3975
  const refine = (id, agent, target, quality, verb = "complete") => {
3975
3976
  const ar = resolveAgent(agent);
@@ -4026,6 +4027,13 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
4026
4027
  reason: "The roadmap has no candidates yet, but ready Work Items exist."
4027
4028
  });
4028
4029
  }
4030
+ if (st.ready_multirepo_wi_ids.length > 0) {
4031
+ sec.push({
4032
+ id: "validate-work-item-modules",
4033
+ label: "Validate affected modules for multirepo Work Items before implementation.",
4034
+ reason: `${st.ready_multirepo_wi_ids.length} ready Work Item(s) reference affected_modules.`
4035
+ });
4036
+ }
4029
4037
  if (adapters === 0) {
4030
4038
  return {
4031
4039
  id: "prepare-implementation",
@@ -7705,7 +7713,8 @@ function mapNextStepId(id) {
7705
7713
  "implement-work-item": "prepare-implementation",
7706
7714
  "refine-module-context": "refine-work-item",
7707
7715
  "init-module-context": "enable-kaddo",
7708
- "module-ready": "prepare-implementation"
7716
+ "module-ready": "prepare-implementation",
7717
+ "validate-work-item-modules": "prepare-implementation"
7709
7718
  };
7710
7719
  return MAP[id] ?? id;
7711
7720
  }
@@ -8527,7 +8536,7 @@ function buildGuardHistory(dir) {
8527
8536
  const d3 = hotspotDir(t.code_path);
8528
8537
  hotspotMap.set(d3, (hotspotMap.get(d3) ?? 0) + 1);
8529
8538
  }
8530
- const hotspots = [...hotspotMap.entries()].map(([path3, warnings]) => ({ path: path3, warnings })).sort((a2, b2) => b2.warnings - a2.warnings);
8539
+ const hotspots = [...hotspotMap.entries()].map(([path4, warnings]) => ({ path: path4, warnings })).sort((a2, b2) => b2.warnings - a2.warnings);
8531
8540
  return {
8532
8541
  available: true,
8533
8542
  total_runs: sorted.length,
@@ -9924,8 +9933,462 @@ function listGraphHints(root, filter = {}) {
9924
9933
  });
9925
9934
  }
9926
9935
 
9927
- // ../cli/src/core/context-pack.ts
9936
+ // src/multirepo.ts
9937
+ import fs3 from "fs";
9938
+ import path3 from "path";
9928
9939
  import matter11 from "gray-matter";
9940
+ import { parse as parseYaml8 } from "yaml";
9941
+ var ok2 = (data) => ({ ok: true, data });
9942
+ var fail2 = (message) => ({ ok: false, message });
9943
+ function loadModulesDescriptor(root) {
9944
+ return readYaml(root, ".kaddo/modules.yml") ?? { modules: [] };
9945
+ }
9946
+ function loadConfig2(root) {
9947
+ return readYaml(root, ".kaddo/config.yml");
9948
+ }
9949
+ function projectRole2(config) {
9950
+ return config.project?.role ?? config.multirepo?.role;
9951
+ }
9952
+ function isCore(config) {
9953
+ return projectRole2(config) === "core";
9954
+ }
9955
+ function readModuleFile(root, repoPath, relFile) {
9956
+ const moduleDir = path3.resolve(root, repoPath);
9957
+ const abs = path3.resolve(moduleDir, relFile);
9958
+ if (!abs.startsWith(moduleDir)) return null;
9959
+ if (!fs3.existsSync(abs) || !fs3.statSync(abs).isFile()) return null;
9960
+ return fs3.readFileSync(abs, "utf-8");
9961
+ }
9962
+ function detectModuleStatus2(root, repoPath) {
9963
+ const moduleDir = path3.resolve(root, repoPath);
9964
+ if (!fs3.existsSync(moduleDir)) {
9965
+ return { status: "missing", configured: false, moduleContext: false, currentState: false, codebase: false, warning: `Directory \`${repoPath}\` does not exist.` };
9966
+ }
9967
+ const configPath = path3.join(moduleDir, ".kaddo", "config.yml");
9968
+ if (!fs3.existsSync(configPath)) {
9969
+ return { status: "not_configured", configured: false, moduleContext: false, currentState: false, codebase: false, warning: `Run \`kaddo init\` inside \`${repoPath}\` and select multirepo \u2192 module.` };
9970
+ }
9971
+ let parsed = null;
9972
+ try {
9973
+ parsed = parseYaml8(fs3.readFileSync(configPath, "utf-8"));
9974
+ } catch {
9975
+ }
9976
+ const role = parsed?.project?.role ?? parsed?.multirepo?.role;
9977
+ if (role !== "module") {
9978
+ return { status: "invalid", configured: true, role, moduleContext: false, currentState: false, codebase: false, warning: `Repository has Kaddo configured but project.role is not module.` };
9979
+ }
9980
+ return {
9981
+ status: "configured",
9982
+ configured: true,
9983
+ role: "module",
9984
+ moduleContext: fs3.existsSync(path3.join(moduleDir, "knowledge", "module", "module-context.md")),
9985
+ currentState: fs3.existsSync(path3.join(moduleDir, "knowledge", "tech", "current-state.md")),
9986
+ codebase: fs3.existsSync(path3.join(moduleDir, "knowledge", "tech", "codebase.md"))
9987
+ };
9988
+ }
9989
+ function isPlaceholder(content) {
9990
+ const body = content.replace(/^---[\s\S]*?---\n?/, "");
9991
+ return /^_[^_]+_$/m.test(body) && !body.split("\n").some((l2) => l2.trim().length > 0 && !l2.startsWith("#") && !l2.startsWith("_") && !l2.startsWith("-") && !l2.startsWith(">"));
9992
+ }
9993
+ function firstParagraph3(md) {
9994
+ const body = md.replace(/^---[\s\S]*?---\n?/, "");
9995
+ for (const block of body.split(/\n\s*\n/)) {
9996
+ const t = block.trim();
9997
+ if (t && !t.startsWith("#") && !t.startsWith(">") && !t.startsWith("_")) return t.replace(/\s+/g, " ");
9998
+ }
9999
+ return "";
10000
+ }
10001
+ function slugify2(s) {
10002
+ return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
10003
+ }
10004
+ function modulesListTool(root, args) {
10005
+ const config = loadConfig2(root);
10006
+ if (!config) return fail2("No Kaddo config found. Run `kaddo init` first.");
10007
+ if (!isCore(config)) return fail2("kaddo_modules_list can only be called from a core repository (project.role: core).");
10008
+ const descriptor = loadModulesDescriptor(root);
10009
+ const modules = (descriptor.modules ?? []).map((m2) => {
10010
+ const st = detectModuleStatus2(root, m2.repoPath);
10011
+ const entry = {
10012
+ id: m2.id,
10013
+ repo: m2.repoPath,
10014
+ status: st.status,
10015
+ kaddo: {
10016
+ configured: st.configured,
10017
+ role: st.role,
10018
+ moduleContext: st.moduleContext,
10019
+ currentState: st.currentState,
10020
+ codebase: st.codebase
10021
+ }
10022
+ };
10023
+ if (args.includeWarnings && st.warning) entry.warning = st.warning;
10024
+ return entry;
10025
+ });
10026
+ return ok2({
10027
+ project: { name: config.project?.name ?? "unknown", structure: config.project?.structure ?? "unknown", role: "core" },
10028
+ modules
10029
+ });
10030
+ }
10031
+ function getModuleContextTool(root, args) {
10032
+ const config = loadConfig2(root);
10033
+ if (!config) return fail2("No Kaddo config found.");
10034
+ const descriptor = loadModulesDescriptor(root);
10035
+ const mod = (descriptor.modules ?? []).find((m2) => m2.id === args.module);
10036
+ if (!mod) return fail2(`Module "${args.module}" not found in .kaddo/modules.yml.`);
10037
+ const st = detectModuleStatus2(root, mod.repoPath);
10038
+ const warnings = [];
10039
+ if (st.status !== "configured") {
10040
+ if (st.warning) warnings.push(st.warning);
10041
+ return ok2({
10042
+ module: { id: mod.id, status: st.status },
10043
+ context: null,
10044
+ warnings,
10045
+ recommendedNextStep: { label: "Initialize module repository with Kaddo.", command: "kaddo init" }
10046
+ });
10047
+ }
10048
+ const mcContent = readModuleFile(root, mod.repoPath, "knowledge/module/module-context.md");
10049
+ const context = {
10050
+ moduleContextPath: `${mod.repoPath}/knowledge/module/module-context.md`,
10051
+ moduleContext: mcContent ?? null
10052
+ };
10053
+ if (mcContent && isPlaceholder(mcContent)) {
10054
+ warnings.push("module-context.md still contains placeholder sections.");
10055
+ }
10056
+ if (args.includeTech) {
10057
+ const cs = readModuleFile(root, mod.repoPath, "knowledge/tech/current-state.md");
10058
+ const cb = readModuleFile(root, mod.repoPath, "knowledge/tech/codebase.md");
10059
+ context.currentStatePath = `${mod.repoPath}/knowledge/tech/current-state.md`;
10060
+ context.currentStateSummary = cs ? firstParagraph3(cs) : null;
10061
+ context.codebasePath = `${mod.repoPath}/knowledge/tech/codebase.md`;
10062
+ context.codebaseSummary = cb ? firstParagraph3(cb) : null;
10063
+ if (!cs) warnings.push("current-state.md not found in module repository.");
10064
+ if (!cb) warnings.push("codebase.md not found in module repository.");
10065
+ }
10066
+ return ok2({
10067
+ module: { id: mod.id, repo: mod.repoPath, status: "configured" },
10068
+ context,
10069
+ warnings: args.includeWarnings ? warnings : []
10070
+ });
10071
+ }
10072
+ function validateWorkItemModulesTool(root, args) {
10073
+ const items = listWorkItems(root);
10074
+ const wi = items.find((w3) => w3.id === args.workItemId);
10075
+ if (!wi) return fail2(`Work Item "${args.workItemId}" not found.`);
10076
+ const raw = readText(root, wi.path);
10077
+ if (!raw) return fail2(`Could not read Work Item file.`);
10078
+ const parsed = matter11(raw);
10079
+ const data = parsed.data;
10080
+ const affectedModules = Array.isArray(data.affected_modules) ? data.affected_modules.map(String) : [];
10081
+ const codeEntries = Array.isArray(data.code) ? data.code.filter((c) => typeof c === "object" && c !== null).map((c) => ({ repo: typeof c.repo === "string" ? c.repo : void 0, paths: Array.isArray(c.paths) ? c.paths.map(String) : [] })) : [];
10082
+ const descriptor = loadModulesDescriptor(root);
10083
+ const mapped = new Set((descriptor.modules ?? []).map((m2) => m2.id));
10084
+ const warnings = [];
10085
+ const errors = [];
10086
+ if (affectedModules.length === 0) {
10087
+ warnings.push("Work Item has no affected_modules declared.");
10088
+ }
10089
+ const moduleValidations = affectedModules.map((modId) => {
10090
+ if (!mapped.has(modId)) {
10091
+ errors.push(`Module "${modId}" is not in .kaddo/modules.yml.`);
10092
+ return { id: modId, status: "not_mapped", hasModuleContext: false, hasCurrentState: false, hasCodebase: false, hasOwnership: false };
10093
+ }
10094
+ const mod = (descriptor.modules ?? []).find((m2) => m2.id === modId);
10095
+ const st = detectModuleStatus2(root, mod.repoPath);
10096
+ if (st.status !== "configured") {
10097
+ warnings.push(`Module "${modId}" is ${st.status}.`);
10098
+ }
10099
+ const mcContent = st.moduleContext ? readModuleFile(root, mod.repoPath, "knowledge/module/module-context.md") : null;
10100
+ if (mcContent && isPlaceholder(mcContent)) {
10101
+ warnings.push(`Module "${modId}" has placeholder module-context.`);
10102
+ }
10103
+ const hasOwnership = codeEntries.some((c) => c.repo && (c.repo === mod.repoPath || c.repo.endsWith(`/${modId}`) || c.repo.includes(modId)));
10104
+ return { id: modId, status: st.status, hasModuleContext: st.moduleContext, hasCurrentState: st.currentState, hasCodebase: st.codebase, hasOwnership };
10105
+ });
10106
+ for (const ce of codeEntries) {
10107
+ if (ce.repo && !affectedModules.some((m2) => ce.repo.includes(m2))) {
10108
+ warnings.push(`code.repo "${ce.repo}" is not listed in affected_modules.`);
10109
+ }
10110
+ }
10111
+ for (const mv of moduleValidations) {
10112
+ if (affectedModules.includes(mv.id) && !mv.hasOwnership) {
10113
+ warnings.push(`Module "${mv.id}" is in affected_modules but has no paths in code.`);
10114
+ }
10115
+ }
10116
+ return ok2({
10117
+ workItem: { id: wi.id, title: wi.title, status: wi.status, scope: affectedModules.length > 0 ? "multirepo" : "single", affectedModules },
10118
+ validation: { valid: errors.length === 0, modules: moduleValidations, warnings, errors }
10119
+ });
10120
+ }
10121
+ function getWorkItemContextTool(root, args) {
10122
+ const config = loadConfig2(root);
10123
+ if (!config) return fail2("No Kaddo config found.");
10124
+ const items = listWorkItems(root);
10125
+ const wi = items.find((w3) => w3.id === args.workItemId);
10126
+ if (!wi) return fail2(`Work Item "${args.workItemId}" not found.`);
10127
+ const raw = readText(root, wi.path);
10128
+ if (!raw) return fail2(`Could not read Work Item file.`);
10129
+ const parsed = matter11(raw);
10130
+ const data = parsed.data;
10131
+ const affectedModules = Array.isArray(data.affected_modules) ? data.affected_modules.map(String) : [];
10132
+ const codeEntries = Array.isArray(data.code) ? data.code.filter((c) => typeof c === "object" && c !== null).map((c) => ({ repo: typeof c.repo === "string" ? c.repo : void 0, paths: Array.isArray(c.paths) ? c.paths.map(String) : [] })) : [];
10133
+ const descriptor = loadModulesDescriptor(root);
10134
+ const warnings = [];
10135
+ const modules = affectedModules.map((modId) => {
10136
+ const mod = (descriptor.modules ?? []).find((m2) => m2.id === modId);
10137
+ if (!mod) {
10138
+ warnings.push(`Module "${modId}" not in modules.yml.`);
10139
+ return { id: modId, repo: null, moduleContext: null, currentStateSummary: null, codebaseSummary: null };
10140
+ }
10141
+ const st = detectModuleStatus2(root, mod.repoPath);
10142
+ if (st.status !== "configured") warnings.push(`Module "${modId}" is ${st.status}.`);
10143
+ const mc = args.includeModuleContexts !== false ? readModuleFile(root, mod.repoPath, "knowledge/module/module-context.md") : null;
10144
+ const cs = readModuleFile(root, mod.repoPath, "knowledge/tech/current-state.md");
10145
+ const cb = readModuleFile(root, mod.repoPath, "knowledge/tech/codebase.md");
10146
+ return { id: modId, repo: mod.repoPath, moduleContext: mc, currentStateSummary: cs ? firstParagraph3(cs) : null, codebaseSummary: cb ? firstParagraph3(cb) : null };
10147
+ });
10148
+ const ownership = { crossRepo: codeEntries.length > 1, items: codeEntries.filter((c) => c.repo).map((c) => ({ repo: c.repo, paths: c.paths ?? [] })) };
10149
+ const result = {
10150
+ project: { name: config.project?.name ?? "unknown", role: projectRole2(config) ?? "unknown", structure: config.project?.structure ?? "unknown" },
10151
+ workItem: { id: wi.id, title: wi.title, status: wi.status, affectedModules, acceptanceCriteria: extractSection(parsed.content, "Acceptance criteria"), outOfScope: extractSection(parsed.content, "Out of scope") },
10152
+ modules,
10153
+ ownership,
10154
+ warnings
10155
+ };
10156
+ if (args.includeBranchStrategy !== false && affectedModules.length > 0) {
10157
+ result.branchStrategy = buildBranchStrategy(wi, affectedModules, codeEntries, descriptor);
10158
+ }
10159
+ if (args.includeGuardExpectations) {
10160
+ result.guardExpectations = [
10161
+ "Run guard from core before closing the Work Item.",
10162
+ "Validate ownership across all affected repos."
10163
+ ];
10164
+ }
10165
+ return ok2(result);
10166
+ }
10167
+ function extractSection(content, heading) {
10168
+ const re = new RegExp(`^##\\s+${heading}\\s*$`, "im");
10169
+ const lines = content.split(/\r?\n/);
10170
+ const start = lines.findIndex((l2) => re.test(l2));
10171
+ if (start < 0) return [];
10172
+ const out = [];
10173
+ for (let i = start + 1; i < lines.length; i++) {
10174
+ if (/^##\s/.test(lines[i])) break;
10175
+ const m2 = lines[i].match(/^\s*-\s*\[[ x]?\]\s*(.+?)\s*$/);
10176
+ if (m2) out.push(m2[1]);
10177
+ }
10178
+ return out;
10179
+ }
10180
+ function buildBranchStrategy(wi, affectedModules, codeEntries, descriptor) {
10181
+ const slug4 = slugify2(wi.title);
10182
+ const wiLower = wi.id.toLowerCase();
10183
+ const coreBranch = `feature/${wiLower}-${slug4}`;
10184
+ const moduleBranches = {};
10185
+ const commitMessages = {};
10186
+ for (const modId of affectedModules) {
10187
+ const mod = (descriptor.modules ?? []).find((m2) => m2.id === modId);
10188
+ const repo = mod?.repoPath ?? modId;
10189
+ moduleBranches[repo] = `feature/${wiLower}-${modId}-${slug4.slice(0, 20)}`;
10190
+ commitMessages[repo] = `fix(${wiLower}): update ${modId}`;
10191
+ }
10192
+ commitMessages["core"] = `docs(${wiLower}): capture cross-repo implementation summary`;
10193
+ return {
10194
+ type: affectedModules.length > 0 ? "multi-repo" : "single-repo",
10195
+ branches: { core: coreBranch, modules: moduleBranches },
10196
+ commitMessages,
10197
+ checklist: [
10198
+ "Create branches manually in each repo.",
10199
+ "Implement changes in affected module repos.",
10200
+ "Run validation per repo.",
10201
+ "Run Kaddo guard from core.",
10202
+ "Review diffs manually.",
10203
+ "Commit only with explicit human confirmation."
10204
+ ]
10205
+ };
10206
+ }
10207
+ function suggestBranchStrategyTool(root, args) {
10208
+ const items = listWorkItems(root);
10209
+ const wi = items.find((w3) => w3.id === args.workItemId);
10210
+ if (!wi) return fail2(`Work Item "${args.workItemId}" not found.`);
10211
+ const raw = readText(root, wi.path);
10212
+ if (!raw) return fail2(`Could not read Work Item file.`);
10213
+ const parsed = matter11(raw);
10214
+ const data = parsed.data;
10215
+ const affectedModules = Array.isArray(data.affected_modules) ? data.affected_modules.map(String) : [];
10216
+ const codeEntries = Array.isArray(data.code) ? data.code.filter((c) => typeof c === "object" && c !== null).map((c) => ({ repo: typeof c.repo === "string" ? c.repo : void 0, paths: Array.isArray(c.paths) ? c.paths.map(String) : [] })) : [];
10217
+ const descriptor = loadModulesDescriptor(root);
10218
+ const strategy = buildBranchStrategy(wi, affectedModules, codeEntries, descriptor);
10219
+ return ok2({
10220
+ workItem: { id: wi.id, title: wi.title },
10221
+ strategy,
10222
+ safety: { gitCommandsExecuted: false, message: "Kaddo only suggests branch and commit strategy. It does not execute git commands." }
10223
+ });
10224
+ }
10225
+ function exportCapsuleTool(root, args) {
10226
+ const config = loadConfig2(root);
10227
+ if (!config) return fail2("No Kaddo config found.");
10228
+ const role = projectRole2(config);
10229
+ const warnings = [];
10230
+ let capsuleType;
10231
+ let capsuleName;
10232
+ let capsuleData;
10233
+ if (args.module) {
10234
+ if (role === "core") {
10235
+ const descriptor = loadModulesDescriptor(root);
10236
+ const mod = (descriptor.modules ?? []).find((m2) => m2.id === args.module);
10237
+ if (!mod) return fail2(`Module "${args.module}" not found in .kaddo/modules.yml.`);
10238
+ const mc = readModuleFile(root, mod.repoPath, "knowledge/module/module-context.md");
10239
+ if (!mc) warnings.push("module-context.md not found for this module.");
10240
+ if (mc && isPlaceholder(mc)) warnings.push("module-context.md contains placeholder sections.");
10241
+ capsuleType = "module";
10242
+ capsuleName = `${slugify2(config.project?.name ?? "project")}-${args.module}`;
10243
+ capsuleData = {
10244
+ type: "knowledge-capsule",
10245
+ system: `${config.project?.name ?? "unknown"}/${args.module}`,
10246
+ version: 1,
10247
+ updated_at: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
10248
+ owner: mod.owner ?? "unknown",
10249
+ source_project: config.project?.name ?? "unknown",
10250
+ purpose: mc ? firstParagraph3(mc) : "",
10251
+ responsibilities: [],
10252
+ capabilities: mod.capabilities ?? [],
10253
+ contracts: [],
10254
+ dependencies: [],
10255
+ knownRisks: [],
10256
+ owners: mod.owner ? [mod.owner] : []
10257
+ };
10258
+ } else if (role === "module") {
10259
+ const mc = readText(root, "knowledge/module/module-context.md");
10260
+ if (!mc) warnings.push("module-context.md not found.");
10261
+ if (mc && isPlaceholder(mc)) warnings.push("module-context.md contains placeholder sections.");
10262
+ const modId = config.module?.id ?? args.module;
10263
+ capsuleType = "module";
10264
+ capsuleName = slugify2(modId);
10265
+ capsuleData = {
10266
+ type: "knowledge-capsule",
10267
+ system: `${config.multirepo?.parent_system ?? "unknown"}/${modId}`,
10268
+ version: 1,
10269
+ updated_at: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
10270
+ purpose: mc ? firstParagraph3(mc) : "",
10271
+ responsibilities: [],
10272
+ capabilities: [],
10273
+ contracts: [],
10274
+ dependencies: [],
10275
+ knownRisks: [],
10276
+ owners: []
10277
+ };
10278
+ } else {
10279
+ return fail2("Cannot export module capsule: project role is neither core nor module.");
10280
+ }
10281
+ } else if (args.scope === "system") {
10282
+ if (role !== "core") return fail2("--scope system can only be used from a core repository.");
10283
+ const descriptor = loadModulesDescriptor(root);
10284
+ const responsibilities = [];
10285
+ for (const m2 of descriptor.modules ?? []) {
10286
+ const mc = readModuleFile(root, m2.repoPath, "knowledge/module/module-context.md");
10287
+ responsibilities.push(mc ? `[${m2.id}] ${firstParagraph3(mc) || m2.name || m2.id}` : `[${m2.id}] ${m2.name || m2.id} (no module-context)`);
10288
+ }
10289
+ capsuleType = "system";
10290
+ capsuleName = `${slugify2(config.project?.name ?? "project")}-system`;
10291
+ capsuleData = {
10292
+ type: "knowledge-capsule",
10293
+ system: config.project?.name ?? "unknown",
10294
+ version: 1,
10295
+ updated_at: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
10296
+ purpose: "",
10297
+ responsibilities,
10298
+ capabilities: [],
10299
+ contracts: [],
10300
+ dependencies: [],
10301
+ knownRisks: [],
10302
+ owners: []
10303
+ };
10304
+ } else {
10305
+ if (role === "module") {
10306
+ const mc = readText(root, "knowledge/module/module-context.md");
10307
+ if (!mc) warnings.push("module-context.md not found.");
10308
+ if (mc && isPlaceholder(mc)) warnings.push("module-context.md contains placeholder sections.");
10309
+ const modId = config.module?.id ?? "module";
10310
+ capsuleType = "module";
10311
+ capsuleName = slugify2(modId);
10312
+ capsuleData = {
10313
+ type: "knowledge-capsule",
10314
+ system: `${config.multirepo?.parent_system ?? "unknown"}/${modId}`,
10315
+ version: 1,
10316
+ updated_at: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
10317
+ purpose: mc ? firstParagraph3(mc) : "",
10318
+ responsibilities: [],
10319
+ capabilities: [],
10320
+ contracts: [],
10321
+ dependencies: [],
10322
+ knownRisks: [],
10323
+ owners: []
10324
+ };
10325
+ } else {
10326
+ capsuleType = "project";
10327
+ capsuleName = slugify2(config.project?.name ?? "project");
10328
+ capsuleData = {
10329
+ type: "knowledge-capsule",
10330
+ system: config.project?.name ?? "unknown",
10331
+ version: 1,
10332
+ updated_at: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
10333
+ purpose: "",
10334
+ responsibilities: [],
10335
+ capabilities: [],
10336
+ contracts: [],
10337
+ dependencies: [],
10338
+ knownRisks: [],
10339
+ owners: []
10340
+ };
10341
+ }
10342
+ }
10343
+ const jsonPath = `.kaddo/exports/${capsuleName}.capsule.json`;
10344
+ const mdPath = `.kaddo/exports/${capsuleName}.capsule.md`;
10345
+ const jsonContent = JSON.stringify(capsuleData, null, 2) + "\n";
10346
+ const mdContent = renderCapsuleMd(capsuleData);
10347
+ writeDerived(root, jsonPath, jsonContent);
10348
+ writeDerived(root, mdPath, mdContent);
10349
+ return ok2({ exported: true, type: capsuleType, module: args.module ?? void 0, paths: { json: jsonPath, md: mdPath }, warnings });
10350
+ }
10351
+ function renderCapsuleMd(data) {
10352
+ const fm = ["---", `type: knowledge-capsule`, `system: ${data.system}`, `version: ${data.version}`, `updated_at: ${data.updated_at}`, "---", ""].join("\n");
10353
+ const list2 = (items) => items.length === 0 ? "_To be completed._" : items.map((i) => `- ${i}`).join("\n");
10354
+ return [
10355
+ fm,
10356
+ `# ${data.system} \u2014 Knowledge Capsule
10357
+ `,
10358
+ `## Purpose
10359
+
10360
+ ${data.purpose || "_To be completed._"}
10361
+ `,
10362
+ `## Responsibilities
10363
+
10364
+ ${list2(data.responsibilities)}
10365
+ `,
10366
+ `## Capabilities
10367
+
10368
+ ${list2(data.capabilities)}
10369
+ `,
10370
+ `## Contracts
10371
+
10372
+ ${list2(data.contracts)}
10373
+ `,
10374
+ `## Dependencies
10375
+
10376
+ ${list2(data.dependencies)}
10377
+ `,
10378
+ `## Known Risks
10379
+
10380
+ ${list2(data.knownRisks)}
10381
+ `,
10382
+ `## Owners
10383
+
10384
+ ${list2(data.owners)}
10385
+ `,
10386
+ "> Security: this capsule must never contain secrets, tokens, credentials, PII or source code.\n"
10387
+ ].join("\n");
10388
+ }
10389
+
10390
+ // ../cli/src/core/context-pack.ts
10391
+ import matter12 from "gray-matter";
9929
10392
  var CONTEXT_PACK_VERSION = "1";
9930
10393
  var ARCH_DIR3 = "knowledge";
9931
10394
  function readScanJson(dir) {
@@ -9937,8 +10400,8 @@ function readScanJson(dir) {
9937
10400
  return null;
9938
10401
  }
9939
10402
  }
9940
- function firstParagraph3(markdown) {
9941
- const body = matter11(markdown).content.trim();
10403
+ function firstParagraph4(markdown) {
10404
+ const body = matter12(markdown).content.trim();
9942
10405
  const para = body.split("\n\n").map((p) => p.trim()).find((p) => p && !p.startsWith("#"));
9943
10406
  return para ?? "";
9944
10407
  }
@@ -9946,7 +10409,7 @@ function readMarkdownSummary(dir, file) {
9946
10409
  const p = join(dir, ARCH_DIR3, file);
9947
10410
  if (!exists(p)) return null;
9948
10411
  try {
9949
- return firstParagraph3(readFile(p));
10412
+ return firstParagraph4(readFile(p));
9950
10413
  } catch {
9951
10414
  return null;
9952
10415
  }
@@ -11112,6 +11575,72 @@ function createServer(root) {
11112
11575
  { title: "Get skill", description: "Get an installed reusable skill by id.", inputSchema: { id: z3.string() } },
11113
11576
  async (args) => toolText(guarded(root, () => getSkillTool(root, args.id)))
11114
11577
  );
11578
+ server.registerTool(
11579
+ "kaddo_modules_list",
11580
+ {
11581
+ title: "List multirepo modules",
11582
+ description: "List mapped modules and their Kaddo configuration status. Only works from a core repository.",
11583
+ inputSchema: { includeWarnings: z3.boolean().optional() }
11584
+ },
11585
+ async (args) => toolText(guarded(root, () => modulesListTool(root, args)))
11586
+ );
11587
+ server.registerTool(
11588
+ "kaddo_get_module_context",
11589
+ {
11590
+ title: "Get module context",
11591
+ description: "Get the local knowledge context (module-context.md, tech files) for a mapped module.",
11592
+ inputSchema: {
11593
+ module: z3.string(),
11594
+ includeTech: z3.boolean().optional(),
11595
+ includeWarnings: z3.boolean().optional()
11596
+ }
11597
+ },
11598
+ async (args) => toolText(guarded(root, () => getModuleContextTool(root, args)))
11599
+ );
11600
+ server.registerTool(
11601
+ "kaddo_validate_work_item_modules",
11602
+ {
11603
+ title: "Validate Work Item modules",
11604
+ description: "Validate that a Work Item has coherent affected_modules and sufficient cross-repo ownership.",
11605
+ inputSchema: { workItemId: z3.string() }
11606
+ },
11607
+ async (args) => toolText(guarded(root, () => validateWorkItemModulesTool(root, args)))
11608
+ );
11609
+ server.registerTool(
11610
+ "kaddo_get_work_item_context",
11611
+ {
11612
+ title: "Get Work Item context",
11613
+ description: "Composite context for implementing a multirepo Work Item: WI details, affected module contexts, ownership, branch strategy.",
11614
+ inputSchema: {
11615
+ workItemId: z3.string(),
11616
+ includeModuleContexts: z3.boolean().optional(),
11617
+ includeBranchStrategy: z3.boolean().optional(),
11618
+ includeGuardExpectations: z3.boolean().optional()
11619
+ }
11620
+ },
11621
+ async (args) => toolText(guarded(root, () => getWorkItemContextTool(root, args)))
11622
+ );
11623
+ server.registerTool(
11624
+ "kaddo_suggest_branch_strategy",
11625
+ {
11626
+ title: "Suggest branch strategy",
11627
+ description: "Suggest branch names, commit messages, and a checklist for a multirepo Work Item. Does NOT execute git.",
11628
+ inputSchema: { workItemId: z3.string() }
11629
+ },
11630
+ async (args) => toolText(guarded(root, () => suggestBranchStrategyTool(root, args)))
11631
+ );
11632
+ server.registerTool(
11633
+ "kaddo_export_capsule",
11634
+ {
11635
+ title: "Export Knowledge Capsule",
11636
+ description: "Export a Knowledge Capsule as a derived artifact under .kaddo/exports/. Supports scope=system (from core) and module=<id> (from core or module).",
11637
+ inputSchema: {
11638
+ scope: z3.string().optional(),
11639
+ module: z3.string().optional()
11640
+ }
11641
+ },
11642
+ async (args) => toolText(guarded(root, () => exportCapsuleTool(root, args)))
11643
+ );
11115
11644
  let installedSkills = [];
11116
11645
  try {
11117
11646
  assertKaddoProject(root);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/mcp",
3
- "version": "3.61.0",
3
+ "version": "3.62.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": {