@kaddo/mcp 3.62.0 → 3.63.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 +173 -44
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -168,7 +168,8 @@ var DERIVED_WRITE_FILES = /* @__PURE__ */ new Set([
168
168
  ".kaddo/graph.json",
169
169
  ".kaddo/graph.mmd",
170
170
  ".kaddo/graph-hints.md",
171
- ".kaddo/graph-hints.json"
171
+ ".kaddo/graph-hints.json",
172
+ ".kaddo/modules.yml"
172
173
  ]);
173
174
  var DERIVED_EXPORTS_RE = /^\.kaddo\/exports\/[^/]+\.capsule\.(md|json)$/;
174
175
  var DERIVED_REPORTS_RE = /^\.kaddo\/reports\/[^/]+\.(md|json)$/;
@@ -447,10 +448,14 @@ var configSchema = z.object({
447
448
  team: z.object({
448
449
  size: teamSizeSchema.default(DEFAULTS.teamSize)
449
450
  }).passthrough().default({}),
451
+ system: z.object({
452
+ name: z.string().optional()
453
+ }).passthrough().optional(),
450
454
  multirepo: z.object({
451
455
  role: multirepoRoleSchema,
452
456
  modules_file: z.string().optional(),
453
- parent_system: z.string().optional()
457
+ parent_system: z.string().optional(),
458
+ workspace_roots: z.array(z.string()).optional()
454
459
  }).passthrough().optional(),
455
460
  module: z.object({
456
461
  id: z.string().optional()
@@ -489,6 +494,9 @@ function projectRole(config) {
489
494
  function isModule(config) {
490
495
  return projectRole(config) === "module";
491
496
  }
497
+ function isCore(config) {
498
+ return projectRole(config) === "core";
499
+ }
492
500
 
493
501
  // ../cli/src/services/artifact-reader.ts
494
502
  import matter3 from "gray-matter";
@@ -790,7 +798,7 @@ function assessPhase(input) {
790
798
 
791
799
  // ../cli/src/core/capsule.ts
792
800
  import matter4 from "gray-matter";
793
- import { parse as parseYaml6, stringify as stringifyYaml2 } from "yaml";
801
+ import { parse as parseYaml7, stringify as stringifyYaml3 } from "yaml";
794
802
 
795
803
  // ../cli/src/services/owners.ts
796
804
  import { parse as parseYaml3 } from "yaml";
@@ -847,7 +855,7 @@ function loadMappedModules(dir) {
847
855
  modules.push({
848
856
  id,
849
857
  name: typeof m2.name === "string" ? m2.name : void 0,
850
- repoPath: typeof m2.repoPath === "string" ? m2.repoPath : "",
858
+ repoPath: typeof m2.repoPath === "string" ? m2.repoPath : typeof m2.path === "string" ? m2.path : "",
851
859
  type: typeof m2.type === "string" ? m2.type : void 0,
852
860
  mainTechnology: typeof m2.mainTechnology === "string" ? m2.mainTechnology : void 0,
853
861
  owner: typeof m2.owner === "string" ? m2.owner : void 0,
@@ -896,13 +904,15 @@ function detectModuleStatus(coreDir, repoPath) {
896
904
  status: "configured",
897
905
  configured: true,
898
906
  role: "module",
899
- moduleContext: exists(join(moduleDir, "knowledge", "module", "module-context.md")),
907
+ moduleContext: exists(join(moduleDir, "knowledge", "tech", "module", "module-context.md")) || exists(join(moduleDir, "knowledge", "module", "module-context.md")),
900
908
  currentState: exists(join(moduleDir, "knowledge", "tech", "current-state.md")),
901
909
  codebase: exists(join(moduleDir, "knowledge", "tech", "codebase.md"))
902
910
  };
903
911
  }
904
912
  function readModuleContext(coreDir, repoPath) {
905
- const p = join(coreDir, repoPath, "knowledge", "module", "module-context.md");
913
+ const newPath = join(coreDir, repoPath, "knowledge", "tech", "module", "module-context.md");
914
+ const legacyPath = join(coreDir, repoPath, "knowledge", "module", "module-context.md");
915
+ const p = exists(newPath) ? newPath : legacyPath;
906
916
  if (!exists(p)) return null;
907
917
  try {
908
918
  return readFile(p);
@@ -1042,7 +1052,7 @@ var Q2 = o("\u25B2", "!");
1042
1052
  var ee = o("\u25A0", "x");
1043
1053
 
1044
1054
  // ../cli/src/commands/modules-map.ts
1045
- import { parse as parseYaml5, stringify as stringifyYaml } from "yaml";
1055
+ import { parse as parseYaml6, stringify as stringifyYaml2 } from "yaml";
1046
1056
 
1047
1057
  // ../cli/src/templates/registry.ts
1048
1058
  var QUALITY = "## Quality checklist";
@@ -2153,6 +2163,9 @@ ${QUALITY}
2153
2163
  - [ ] The next step is clear.
2154
2164
  `;
2155
2165
 
2166
+ // ../cli/src/commands/modules-discover.ts
2167
+ import { parse as parseYaml5, stringify as stringifyYaml } from "yaml";
2168
+
2156
2169
  // ../cli/src/core/capsule.ts
2157
2170
  var KNOWLEDGE2 = "knowledge";
2158
2171
  function today() {
@@ -2252,7 +2265,7 @@ function loadExternalRegistry(dir) {
2252
2265
  const p = join(dir, EXTERNAL_PATH);
2253
2266
  if (!exists(p)) return [];
2254
2267
  try {
2255
- const parsed = parseYaml6(readFile(p));
2268
+ const parsed = parseYaml7(readFile(p));
2256
2269
  return Array.isArray(parsed?.external) ? parsed.external : [];
2257
2270
  } catch {
2258
2271
  return [];
@@ -3693,6 +3706,9 @@ function buildAdapterStatuses(dir) {
3693
3706
  });
3694
3707
  }
3695
3708
 
3709
+ // ../cli/src/core/next-step.ts
3710
+ import { parse as parseYaml8 } from "yaml";
3711
+
3696
3712
  // ../cli/src/agents/groups.ts
3697
3713
  var AGENT_GROUPS = {
3698
3714
  business: ["business-agent.md"],
@@ -3845,6 +3861,14 @@ function buildDeliveryState(dir) {
3845
3861
  adapters_installed: adapters.length
3846
3862
  };
3847
3863
  }
3864
+ function hasRegisteredModules(modulesYmlPath) {
3865
+ try {
3866
+ const parsed = parseYaml8(readFile(modulesYmlPath));
3867
+ return Array.isArray(parsed?.modules) && parsed.modules.length > 0;
3868
+ } catch {
3869
+ return false;
3870
+ }
3871
+ }
3848
3872
  function safeRead(p) {
3849
3873
  try {
3850
3874
  return readFile(p);
@@ -3895,11 +3919,12 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
3895
3919
  const state = config.project.state;
3896
3920
  const q3 = (rel) => analyzeKnowledgeArtifact(dir, rel);
3897
3921
  const moduleRepo = isModule(config);
3898
- const MC = "knowledge/module/module-context.md";
3922
+ const coreRepo = isCore(config);
3923
+ const MC = exists(join(dir, "knowledge/tech/module/module-context.md")) ? "knowledge/tech/module/module-context.md" : "knowledge/module/module-context.md";
3899
3924
  if (moduleRepo) {
3900
3925
  const qMC = q3(MC);
3901
3926
  if (qMC === "missing") {
3902
- return { id: "init-module-context", phase: "Setup", label: "Create `knowledge/module/module-context.md` for this module.", command: "kaddo init", reason: "Module context file is missing. Re-run `kaddo init` as a multirepo module." };
3927
+ return { id: "init-module-context", phase: "Setup", label: "Create `knowledge/tech/module/module-context.md` for this module.", command: "kaddo init", reason: "Module context file is missing. Re-run `kaddo init` as a multirepo module." };
3903
3928
  }
3904
3929
  if ((state === "pre-ai" || state === "legacy") && !exists(join(dir, ".kaddo", "scan.json"))) {
3905
3930
  return { id: "scan", phase: "Discovery", label: "Run `kaddo scan` to capture deterministic signals from the existing code.", command: "kaddo scan", reason: "No scan baseline exists for this existing project yet." };
@@ -3948,9 +3973,15 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
3948
3973
  return { id: "module-ready", phase: "Active Delivery", label: "Module context is complete. Create and manage Work Items from the core repository.", reason: "This module's knowledge is ready. Work Items are created and tracked in the core." };
3949
3974
  }
3950
3975
  const qBusiness = q3(B2), qProduct = q3(P2), qCap = q3(CAP), qCurrentState = q3(CS), qCodebase = q3(CB);
3951
- if (qBusiness === "missing" || qProduct === "missing") {
3976
+ if (!coreRepo && (qBusiness === "missing" || qProduct === "missing")) {
3952
3977
  return { id: "bootstrap", phase: "Setup", label: "Run `kaddo bootstrap` to create the project knowledge baseline.", command: "kaddo bootstrap", reason: "The knowledge baseline is incomplete." };
3953
3978
  }
3979
+ if (coreRepo) {
3980
+ const modulesYmlPath = join(dir, ".kaddo", "modules.yml");
3981
+ if (!exists(modulesYmlPath) || !hasRegisteredModules(modulesYmlPath)) {
3982
+ return { id: "modules-discover", phase: "Setup", label: "Run `kaddo modules discover` to find sibling module repositories.", command: "kaddo modules discover", reason: "No modules are mapped for this core repository." };
3983
+ }
3984
+ }
3954
3985
  const ctx = buildCodexAdapterContext(dir);
3955
3986
  if (!ctx.hasAgents) {
3956
3987
  return { id: "add-agents", phase: "Setup", label: "Run `kaddo add agents` to install the Kaddo agent prompt packs.", command: "kaddo add agents", reason: "No Kaddo agents are installed yet." };
@@ -7714,7 +7745,8 @@ function mapNextStepId(id) {
7714
7745
  "refine-module-context": "refine-work-item",
7715
7746
  "init-module-context": "enable-kaddo",
7716
7747
  "module-ready": "prepare-implementation",
7717
- "validate-work-item-modules": "prepare-implementation"
7748
+ "validate-work-item-modules": "prepare-implementation",
7749
+ "modules-discover": "enable-kaddo"
7718
7750
  };
7719
7751
  return MAP[id] ?? id;
7720
7752
  }
@@ -8981,7 +9013,7 @@ function serializeImpactJson(r2) {
8981
9013
  }
8982
9014
 
8983
9015
  // ../cli/src/core/savings.ts
8984
- import { parse as parseYaml7 } from "yaml";
9016
+ import { parse as parseYaml9 } from "yaml";
8985
9017
  var DEFAULT_ASSUMPTIONS = {
8986
9018
  currency: "USD",
8987
9019
  hourly_cost: 40,
@@ -8999,7 +9031,7 @@ function loadAssumptions(dir) {
8999
9031
  const p = join(dir, SAVINGS_PATH);
9000
9032
  if (!exists(p)) return { assumptions: { ...DEFAULT_ASSUMPTIONS }, source: "default" };
9001
9033
  try {
9002
- const raw = parseYaml7(readFile(p));
9034
+ const raw = parseYaml9(readFile(p));
9003
9035
  const a2 = raw?.assumptions ?? {};
9004
9036
  const t = raw?.team ?? {};
9005
9037
  const num = (v, d3) => typeof v === "number" && Number.isFinite(v) ? v : d3;
@@ -9837,7 +9869,7 @@ function getWorkItem(root, id) {
9837
9869
  const content = readText(root, item.path);
9838
9870
  return ok({ ...item, content: content ?? "" });
9839
9871
  }
9840
- function markWorkItemReady(root, id, confirm) {
9872
+ function markWorkItemReady(root, id, confirm2) {
9841
9873
  const item = listWorkItems(root).find((w3) => w3.id === id);
9842
9874
  if (!item) return fail(`Work Item "${id}" not found.`);
9843
9875
  if (item.status === "ready") {
@@ -9847,7 +9879,7 @@ function markWorkItemReady(root, id, confirm) {
9847
9879
  return fail(`Work Item ${item.id} is in state "${item.status}", not "draft". Only draft Work Items can be marked as ready.`);
9848
9880
  }
9849
9881
  const warnings = item.readiness?.warnings ?? [];
9850
- if (!confirm) {
9882
+ if (!confirm2) {
9851
9883
  return ok({
9852
9884
  status: "needs_confirmation",
9853
9885
  workItem: {
@@ -9937,9 +9969,12 @@ function listGraphHints(root, filter = {}) {
9937
9969
  import fs3 from "fs";
9938
9970
  import path3 from "path";
9939
9971
  import matter11 from "gray-matter";
9940
- import { parse as parseYaml8 } from "yaml";
9972
+ import { parse as parseYaml10, stringify as stringifyYaml4 } from "yaml";
9941
9973
  var ok2 = (data) => ({ ok: true, data });
9942
9974
  var fail2 = (message) => ({ ok: false, message });
9975
+ function moduleRepoPath(m2) {
9976
+ return m2.path ?? m2.repoPath ?? "";
9977
+ }
9943
9978
  function loadModulesDescriptor(root) {
9944
9979
  return readYaml(root, ".kaddo/modules.yml") ?? { modules: [] };
9945
9980
  }
@@ -9949,7 +9984,7 @@ function loadConfig2(root) {
9949
9984
  function projectRole2(config) {
9950
9985
  return config.project?.role ?? config.multirepo?.role;
9951
9986
  }
9952
- function isCore(config) {
9987
+ function isCore2(config) {
9953
9988
  return projectRole2(config) === "core";
9954
9989
  }
9955
9990
  function readModuleFile(root, repoPath, relFile) {
@@ -9970,7 +10005,7 @@ function detectModuleStatus2(root, repoPath) {
9970
10005
  }
9971
10006
  let parsed = null;
9972
10007
  try {
9973
- parsed = parseYaml8(fs3.readFileSync(configPath, "utf-8"));
10008
+ parsed = parseYaml10(fs3.readFileSync(configPath, "utf-8"));
9974
10009
  } catch {
9975
10010
  }
9976
10011
  const role = parsed?.project?.role ?? parsed?.multirepo?.role;
@@ -9981,11 +10016,18 @@ function detectModuleStatus2(root, repoPath) {
9981
10016
  status: "configured",
9982
10017
  configured: true,
9983
10018
  role: "module",
9984
- moduleContext: fs3.existsSync(path3.join(moduleDir, "knowledge", "module", "module-context.md")),
10019
+ moduleContext: fs3.existsSync(path3.join(moduleDir, "knowledge", "tech", "module", "module-context.md")) || fs3.existsSync(path3.join(moduleDir, "knowledge", "module", "module-context.md")),
9985
10020
  currentState: fs3.existsSync(path3.join(moduleDir, "knowledge", "tech", "current-state.md")),
9986
10021
  codebase: fs3.existsSync(path3.join(moduleDir, "knowledge", "tech", "codebase.md"))
9987
10022
  };
9988
10023
  }
10024
+ function resolveModuleContextPath(root, repoPath) {
10025
+ const moduleDir = path3.resolve(root, repoPath);
10026
+ if (fs3.existsSync(path3.join(moduleDir, "knowledge", "tech", "module", "module-context.md"))) {
10027
+ return "knowledge/tech/module/module-context.md";
10028
+ }
10029
+ return "knowledge/module/module-context.md";
10030
+ }
9989
10031
  function isPlaceholder(content) {
9990
10032
  const body = content.replace(/^---[\s\S]*?---\n?/, "");
9991
10033
  return /^_[^_]+_$/m.test(body) && !body.split("\n").some((l2) => l2.trim().length > 0 && !l2.startsWith("#") && !l2.startsWith("_") && !l2.startsWith("-") && !l2.startsWith(">"));
@@ -10004,13 +10046,14 @@ function slugify2(s) {
10004
10046
  function modulesListTool(root, args) {
10005
10047
  const config = loadConfig2(root);
10006
10048
  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).");
10049
+ if (!isCore2(config)) return fail2("kaddo_modules_list can only be called from a core repository (project.role: core).");
10008
10050
  const descriptor = loadModulesDescriptor(root);
10009
10051
  const modules = (descriptor.modules ?? []).map((m2) => {
10010
- const st = detectModuleStatus2(root, m2.repoPath);
10052
+ const rp = moduleRepoPath(m2);
10053
+ const st = detectModuleStatus2(root, rp);
10011
10054
  const entry = {
10012
10055
  id: m2.id,
10013
- repo: m2.repoPath,
10056
+ repo: rp,
10014
10057
  status: st.status,
10015
10058
  kaddo: {
10016
10059
  configured: st.configured,
@@ -10034,7 +10077,8 @@ function getModuleContextTool(root, args) {
10034
10077
  const descriptor = loadModulesDescriptor(root);
10035
10078
  const mod = (descriptor.modules ?? []).find((m2) => m2.id === args.module);
10036
10079
  if (!mod) return fail2(`Module "${args.module}" not found in .kaddo/modules.yml.`);
10037
- const st = detectModuleStatus2(root, mod.repoPath);
10080
+ const rp = moduleRepoPath(mod);
10081
+ const st = detectModuleStatus2(root, rp);
10038
10082
  const warnings = [];
10039
10083
  if (st.status !== "configured") {
10040
10084
  if (st.warning) warnings.push(st.warning);
@@ -10045,26 +10089,27 @@ function getModuleContextTool(root, args) {
10045
10089
  recommendedNextStep: { label: "Initialize module repository with Kaddo.", command: "kaddo init" }
10046
10090
  });
10047
10091
  }
10048
- const mcContent = readModuleFile(root, mod.repoPath, "knowledge/module/module-context.md");
10092
+ const mcRelPath = resolveModuleContextPath(root, rp);
10093
+ const mcContent = readModuleFile(root, rp, mcRelPath);
10049
10094
  const context = {
10050
- moduleContextPath: `${mod.repoPath}/knowledge/module/module-context.md`,
10095
+ moduleContextPath: `${rp}/${mcRelPath}`,
10051
10096
  moduleContext: mcContent ?? null
10052
10097
  };
10053
10098
  if (mcContent && isPlaceholder(mcContent)) {
10054
10099
  warnings.push("module-context.md still contains placeholder sections.");
10055
10100
  }
10056
10101
  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`;
10102
+ const cs = readModuleFile(root, rp, "knowledge/tech/current-state.md");
10103
+ const cb = readModuleFile(root, rp, "knowledge/tech/codebase.md");
10104
+ context.currentStatePath = `${rp}/knowledge/tech/current-state.md`;
10060
10105
  context.currentStateSummary = cs ? firstParagraph3(cs) : null;
10061
- context.codebasePath = `${mod.repoPath}/knowledge/tech/codebase.md`;
10106
+ context.codebasePath = `${rp}/knowledge/tech/codebase.md`;
10062
10107
  context.codebaseSummary = cb ? firstParagraph3(cb) : null;
10063
10108
  if (!cs) warnings.push("current-state.md not found in module repository.");
10064
10109
  if (!cb) warnings.push("codebase.md not found in module repository.");
10065
10110
  }
10066
10111
  return ok2({
10067
- module: { id: mod.id, repo: mod.repoPath, status: "configured" },
10112
+ module: { id: mod.id, repo: rp, status: "configured" },
10068
10113
  context,
10069
10114
  warnings: args.includeWarnings ? warnings : []
10070
10115
  });
@@ -10092,15 +10137,17 @@ function validateWorkItemModulesTool(root, args) {
10092
10137
  return { id: modId, status: "not_mapped", hasModuleContext: false, hasCurrentState: false, hasCodebase: false, hasOwnership: false };
10093
10138
  }
10094
10139
  const mod = (descriptor.modules ?? []).find((m2) => m2.id === modId);
10095
- const st = detectModuleStatus2(root, mod.repoPath);
10140
+ const rp = moduleRepoPath(mod);
10141
+ const st = detectModuleStatus2(root, rp);
10096
10142
  if (st.status !== "configured") {
10097
10143
  warnings.push(`Module "${modId}" is ${st.status}.`);
10098
10144
  }
10099
- const mcContent = st.moduleContext ? readModuleFile(root, mod.repoPath, "knowledge/module/module-context.md") : null;
10145
+ const mcRelPath = resolveModuleContextPath(root, rp);
10146
+ const mcContent = st.moduleContext ? readModuleFile(root, rp, mcRelPath) : null;
10100
10147
  if (mcContent && isPlaceholder(mcContent)) {
10101
10148
  warnings.push(`Module "${modId}" has placeholder module-context.`);
10102
10149
  }
10103
- const hasOwnership = codeEntries.some((c) => c.repo && (c.repo === mod.repoPath || c.repo.endsWith(`/${modId}`) || c.repo.includes(modId)));
10150
+ const hasOwnership = codeEntries.some((c) => c.repo && (c.repo === rp || c.repo.endsWith(`/${modId}`) || c.repo.includes(modId)));
10104
10151
  return { id: modId, status: st.status, hasModuleContext: st.moduleContext, hasCurrentState: st.currentState, hasCodebase: st.codebase, hasOwnership };
10105
10152
  });
10106
10153
  for (const ce of codeEntries) {
@@ -10138,12 +10185,14 @@ function getWorkItemContextTool(root, args) {
10138
10185
  warnings.push(`Module "${modId}" not in modules.yml.`);
10139
10186
  return { id: modId, repo: null, moduleContext: null, currentStateSummary: null, codebaseSummary: null };
10140
10187
  }
10141
- const st = detectModuleStatus2(root, mod.repoPath);
10188
+ const rp = moduleRepoPath(mod);
10189
+ const st = detectModuleStatus2(root, rp);
10142
10190
  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 };
10191
+ const mcRelPath = resolveModuleContextPath(root, rp);
10192
+ const mc = args.includeModuleContexts !== false ? readModuleFile(root, rp, mcRelPath) : null;
10193
+ const cs = readModuleFile(root, rp, "knowledge/tech/current-state.md");
10194
+ const cb = readModuleFile(root, rp, "knowledge/tech/codebase.md");
10195
+ return { id: modId, repo: rp, moduleContext: mc, currentStateSummary: cs ? firstParagraph3(cs) : null, codebaseSummary: cb ? firstParagraph3(cb) : null };
10147
10196
  });
10148
10197
  const ownership = { crossRepo: codeEntries.length > 1, items: codeEntries.filter((c) => c.repo).map((c) => ({ repo: c.repo, paths: c.paths ?? [] })) };
10149
10198
  const result = {
@@ -10185,7 +10234,7 @@ function buildBranchStrategy(wi, affectedModules, codeEntries, descriptor) {
10185
10234
  const commitMessages = {};
10186
10235
  for (const modId of affectedModules) {
10187
10236
  const mod = (descriptor.modules ?? []).find((m2) => m2.id === modId);
10188
- const repo = mod?.repoPath ?? modId;
10237
+ const repo = mod ? moduleRepoPath(mod) || modId : modId;
10189
10238
  moduleBranches[repo] = `feature/${wiLower}-${modId}-${slug4.slice(0, 20)}`;
10190
10239
  commitMessages[repo] = `fix(${wiLower}): update ${modId}`;
10191
10240
  }
@@ -10235,7 +10284,9 @@ function exportCapsuleTool(root, args) {
10235
10284
  const descriptor = loadModulesDescriptor(root);
10236
10285
  const mod = (descriptor.modules ?? []).find((m2) => m2.id === args.module);
10237
10286
  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");
10287
+ const rp = moduleRepoPath(mod);
10288
+ const mcRelPath = resolveModuleContextPath(root, rp);
10289
+ const mc = readModuleFile(root, rp, mcRelPath);
10239
10290
  if (!mc) warnings.push("module-context.md not found for this module.");
10240
10291
  if (mc && isPlaceholder(mc)) warnings.push("module-context.md contains placeholder sections.");
10241
10292
  capsuleType = "module";
@@ -10256,7 +10307,7 @@ function exportCapsuleTool(root, args) {
10256
10307
  owners: mod.owner ? [mod.owner] : []
10257
10308
  };
10258
10309
  } else if (role === "module") {
10259
- const mc = readText(root, "knowledge/module/module-context.md");
10310
+ const mc = readText(root, "knowledge/tech/module/module-context.md") ?? readText(root, "knowledge/module/module-context.md");
10260
10311
  if (!mc) warnings.push("module-context.md not found.");
10261
10312
  if (mc && isPlaceholder(mc)) warnings.push("module-context.md contains placeholder sections.");
10262
10313
  const modId = config.module?.id ?? args.module;
@@ -10283,7 +10334,9 @@ function exportCapsuleTool(root, args) {
10283
10334
  const descriptor = loadModulesDescriptor(root);
10284
10335
  const responsibilities = [];
10285
10336
  for (const m2 of descriptor.modules ?? []) {
10286
- const mc = readModuleFile(root, m2.repoPath, "knowledge/module/module-context.md");
10337
+ const rp = moduleRepoPath(m2);
10338
+ const mcRelPath = resolveModuleContextPath(root, rp);
10339
+ const mc = readModuleFile(root, rp, mcRelPath);
10287
10340
  responsibilities.push(mc ? `[${m2.id}] ${firstParagraph3(mc) || m2.name || m2.id}` : `[${m2.id}] ${m2.name || m2.id} (no module-context)`);
10288
10341
  }
10289
10342
  capsuleType = "system";
@@ -10303,7 +10356,7 @@ function exportCapsuleTool(root, args) {
10303
10356
  };
10304
10357
  } else {
10305
10358
  if (role === "module") {
10306
- const mc = readText(root, "knowledge/module/module-context.md");
10359
+ const mc = readText(root, "knowledge/tech/module/module-context.md") ?? readText(root, "knowledge/module/module-context.md");
10307
10360
  if (!mc) warnings.push("module-context.md not found.");
10308
10361
  if (mc && isPlaceholder(mc)) warnings.push("module-context.md contains placeholder sections.");
10309
10362
  const modId = config.module?.id ?? "module";
@@ -10348,6 +10401,70 @@ function exportCapsuleTool(root, args) {
10348
10401
  writeDerived(root, mdPath, mdContent);
10349
10402
  return ok2({ exported: true, type: capsuleType, module: args.module ?? void 0, paths: { json: jsonPath, md: mdPath }, warnings });
10350
10403
  }
10404
+ function modulesDiscoverTool(root, args) {
10405
+ const config = loadConfig2(root);
10406
+ if (!config) return fail2("No Kaddo config found.");
10407
+ if (!isCore2(config)) return fail2("Module discovery is only available from a core repository.");
10408
+ const sysName = config.system?.name ?? config.project?.name ?? "";
10409
+ const workspaceRoots2 = config.multirepo?.workspace_roots ?? [".."];
10410
+ const IGNORED = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", "coverage", "vendor", ".next", "out", "tmp", "temp", ".kaddo"]);
10411
+ const coreAbs = path3.resolve(root);
10412
+ const discovered = [];
10413
+ for (const wr of workspaceRoots2) {
10414
+ const rootAbs = path3.resolve(root, wr);
10415
+ if (!fs3.existsSync(rootAbs) || !fs3.statSync(rootAbs).isDirectory()) continue;
10416
+ let entries;
10417
+ try {
10418
+ entries = fs3.readdirSync(rootAbs);
10419
+ } catch {
10420
+ continue;
10421
+ }
10422
+ for (const entry of entries) {
10423
+ if (IGNORED.has(entry)) continue;
10424
+ const candidateAbs = path3.resolve(rootAbs, entry);
10425
+ if (!fs3.statSync(candidateAbs).isDirectory()) continue;
10426
+ if (candidateAbs === coreAbs) continue;
10427
+ const relPath = path3.relative(root, candidateAbs).replace(/\\/g, "/");
10428
+ const cfgPath = path3.join(candidateAbs, ".kaddo", "config.yml");
10429
+ if (!fs3.existsSync(cfgPath)) {
10430
+ discovered.push({ id: entry, path: relPath, status: "not_configured", eligibleForMapping: false });
10431
+ continue;
10432
+ }
10433
+ let parsed = null;
10434
+ try {
10435
+ parsed = parseYaml10(fs3.readFileSync(cfgPath, "utf-8"));
10436
+ } catch {
10437
+ }
10438
+ const role = parsed?.project?.role ?? parsed?.multirepo?.role;
10439
+ if (role !== "module") {
10440
+ discovered.push({ id: entry, path: relPath, status: "invalid", eligibleForMapping: false, warning: "Not configured as module." });
10441
+ continue;
10442
+ }
10443
+ const modParent = parsed?.multirepo?.parent_system ?? "";
10444
+ if (modParent && modParent !== sysName) {
10445
+ discovered.push({ id: parsed?.module?.id ?? entry, path: relPath, status: "foreign_system", eligibleForMapping: false, parentSystem: modParent });
10446
+ continue;
10447
+ }
10448
+ discovered.push({ id: parsed?.module?.id ?? entry, path: relPath, status: "configured", eligibleForMapping: true });
10449
+ }
10450
+ }
10451
+ if (args.apply) {
10452
+ if (!args.confirm) {
10453
+ return ok2({ discovered, applied: false, message: "Set confirm: true to apply discovered modules." });
10454
+ }
10455
+ const descriptor = loadModulesDescriptor(root);
10456
+ for (const d3 of discovered.filter((dd) => dd.eligibleForMapping)) {
10457
+ if (!(descriptor.modules ?? []).some((m2) => m2.id === d3.id)) {
10458
+ if (!descriptor.modules) descriptor.modules = [];
10459
+ descriptor.modules.push({ id: d3.id, name: d3.id, path: d3.path, parent_system: sysName, status: "configured" });
10460
+ }
10461
+ }
10462
+ const ymlContent = stringifyYaml4({ version: descriptor.version ?? 1, system: sysName, workspace_roots: workspaceRoots2, modules: descriptor.modules ?? [] });
10463
+ writeDerived(root, ".kaddo/modules.yml", ymlContent);
10464
+ return ok2({ discovered, applied: true });
10465
+ }
10466
+ return ok2({ discovered, applied: false });
10467
+ }
10351
10468
  function renderCapsuleMd(data) {
10352
10469
  const fm = ["---", `type: knowledge-capsule`, `system: ${data.system}`, `version: ${data.version}`, `updated_at: ${data.updated_at}`, "---", ""].join("\n");
10353
10470
  const list2 = (items) => items.length === 0 ? "_To be completed._" : items.map((i) => `- ${i}`).join("\n");
@@ -11641,6 +11758,18 @@ function createServer(root) {
11641
11758
  },
11642
11759
  async (args) => toolText(guarded(root, () => exportCapsuleTool(root, args)))
11643
11760
  );
11761
+ server.registerTool(
11762
+ "kaddo_modules_discover",
11763
+ {
11764
+ title: "Discover multirepo modules",
11765
+ description: "Discover sibling repositories configured as Kaddo modules. Does not persist without apply+confirm. Core only.",
11766
+ inputSchema: {
11767
+ apply: z3.boolean().optional(),
11768
+ confirm: z3.boolean().optional()
11769
+ }
11770
+ },
11771
+ async (args) => toolText(guarded(root, () => modulesDiscoverTool(root, args)))
11772
+ );
11644
11773
  let installedSkills = [];
11645
11774
  try {
11646
11775
  assertKaddoProject(root);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/mcp",
3
- "version": "3.62.0",
3
+ "version": "3.63.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": {