@kaddo/mcp 3.61.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 +692 -34
  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()
@@ -475,8 +480,8 @@ function loadConfig(dir) {
475
480
  const parsed = configSchema.safeParse(raw ?? {});
476
481
  if (!parsed.success) {
477
482
  const issues = parsed.error.issues.map((i) => {
478
- const path3 = i.path.join(".");
479
- return path3 ? ` - ${path3}: ${i.message}` : ` - ${i.message}`;
483
+ const path4 = i.path.join(".");
484
+ return path4 ? ` - ${path4}: ${i.message}` : ` - ${i.message}`;
480
485
  }).join("\n");
481
486
  throw new ConfigError(`Invalid .kaddo/config.yml:
482
487
  ${issues}`);
@@ -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";
@@ -829,11 +837,11 @@ function moduleArtifactCoverage(dir, id) {
829
837
  };
830
838
  }
831
839
  function loadMappedModules(dir) {
832
- const path3 = join(dir, DESCRIPTOR_PATH);
833
- if (!exists(path3)) return [];
840
+ const path4 = join(dir, DESCRIPTOR_PATH);
841
+ if (!exists(path4)) return [];
834
842
  let parsed;
835
843
  try {
836
- parsed = parseYaml4(readFile(path3));
844
+ parsed = parseYaml4(readFile(path4));
837
845
  } catch {
838
846
  return [];
839
847
  }
@@ -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 [];
@@ -2283,7 +2296,7 @@ function sectionParagraph2(md, title) {
2283
2296
  }
2284
2297
  return "";
2285
2298
  }
2286
- function parseCapsule(id, path3, md) {
2299
+ function parseCapsule(id, path4, md) {
2287
2300
  const { data } = matter4(md);
2288
2301
  const updatedAt = data.updated_at ? String(data.updated_at) : void 0;
2289
2302
  let ageDays = null;
@@ -2293,7 +2306,7 @@ function parseCapsule(id, path3, md) {
2293
2306
  }
2294
2307
  return {
2295
2308
  id,
2296
- path: path3,
2309
+ path: path4,
2297
2310
  system: data.system ? String(data.system) : id,
2298
2311
  owner: data.owner ? String(data.owner) : void 0,
2299
2312
  updatedAt,
@@ -2355,9 +2368,9 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
2355
2368
  ];
2356
2369
  const presentLayers = [];
2357
2370
  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 });
2371
+ const path4 = layer2.files.map((f3) => `${KNOWLEDGE3}/${f3}`).find((rel) => exists(join(dir, rel)));
2372
+ if (path4) {
2373
+ addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path: path4 });
2361
2374
  presentLayers.push(layer2.id);
2362
2375
  }
2363
2376
  }
@@ -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"],
@@ -3834,6 +3850,7 @@ function buildDeliveryState(dir) {
3834
3850
  refined_draft_ids: wis.filter((w3) => w3.lifecycle === "draft" && w3.rawFrontmatter.refined_by).map((w3) => w3.id),
3835
3851
  ready_work_items: byState("ready"),
3836
3852
  ready_work_item_ids: wis.filter((w3) => w3.lifecycle === "ready").map((w3) => w3.id),
3853
+ 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
3854
  in_progress_work_items: byState("in-progress"),
3838
3855
  blocked_work_items: byState("blocked"),
3839
3856
  total_work_items: total,
@@ -3844,6 +3861,14 @@ function buildDeliveryState(dir) {
3844
3861
  adapters_installed: adapters.length
3845
3862
  };
3846
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
+ }
3847
3872
  function safeRead(p) {
3848
3873
  try {
3849
3874
  return readFile(p);
@@ -3894,11 +3919,12 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
3894
3919
  const state = config.project.state;
3895
3920
  const q3 = (rel) => analyzeKnowledgeArtifact(dir, rel);
3896
3921
  const moduleRepo = isModule(config);
3897
- 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";
3898
3924
  if (moduleRepo) {
3899
3925
  const qMC = q3(MC);
3900
3926
  if (qMC === "missing") {
3901
- 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." };
3902
3928
  }
3903
3929
  if ((state === "pre-ai" || state === "legacy") && !exists(join(dir, ".kaddo", "scan.json"))) {
3904
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." };
@@ -3947,9 +3973,15 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
3947
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." };
3948
3974
  }
3949
3975
  const qBusiness = q3(B2), qProduct = q3(P2), qCap = q3(CAP), qCurrentState = q3(CS), qCodebase = q3(CB);
3950
- if (qBusiness === "missing" || qProduct === "missing") {
3976
+ if (!coreRepo && (qBusiness === "missing" || qProduct === "missing")) {
3951
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." };
3952
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
+ }
3953
3985
  const ctx = buildCodexAdapterContext(dir);
3954
3986
  if (!ctx.hasAgents) {
3955
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." };
@@ -3966,10 +3998,10 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
3966
3998
  const discovery = state === "pre-ai" || state === "legacy";
3967
3999
  const resolveAgent = (agent) => {
3968
4000
  const file = agent.endsWith(".md") ? agent : `${agent}.md`;
3969
- const path3 = agentInstallPath(file);
3970
- const installed = isFile(join(dir, path3));
4001
+ const path4 = agentInstallPath(file);
4002
+ const installed = isFile(join(dir, path4));
3971
4003
  const group = agentGroupOf(file);
3972
- return { agentPath: path3, agentInstalled: installed, installCommand: installed ? void 0 : `kaddo add agents --group ${group}` };
4004
+ return { agentPath: path4, agentInstalled: installed, installCommand: installed ? void 0 : `kaddo add agents --group ${group}` };
3973
4005
  };
3974
4006
  const refine = (id, agent, target, quality, verb = "complete") => {
3975
4007
  const ar = resolveAgent(agent);
@@ -4026,6 +4058,13 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
4026
4058
  reason: "The roadmap has no candidates yet, but ready Work Items exist."
4027
4059
  });
4028
4060
  }
4061
+ if (st.ready_multirepo_wi_ids.length > 0) {
4062
+ sec.push({
4063
+ id: "validate-work-item-modules",
4064
+ label: "Validate affected modules for multirepo Work Items before implementation.",
4065
+ reason: `${st.ready_multirepo_wi_ids.length} ready Work Item(s) reference affected_modules.`
4066
+ });
4067
+ }
4029
4068
  if (adapters === 0) {
4030
4069
  return {
4031
4070
  id: "prepare-implementation",
@@ -7705,7 +7744,9 @@ function mapNextStepId(id) {
7705
7744
  "implement-work-item": "prepare-implementation",
7706
7745
  "refine-module-context": "refine-work-item",
7707
7746
  "init-module-context": "enable-kaddo",
7708
- "module-ready": "prepare-implementation"
7747
+ "module-ready": "prepare-implementation",
7748
+ "validate-work-item-modules": "prepare-implementation",
7749
+ "modules-discover": "enable-kaddo"
7709
7750
  };
7710
7751
  return MAP[id] ?? id;
7711
7752
  }
@@ -8527,7 +8568,7 @@ function buildGuardHistory(dir) {
8527
8568
  const d3 = hotspotDir(t.code_path);
8528
8569
  hotspotMap.set(d3, (hotspotMap.get(d3) ?? 0) + 1);
8529
8570
  }
8530
- const hotspots = [...hotspotMap.entries()].map(([path3, warnings]) => ({ path: path3, warnings })).sort((a2, b2) => b2.warnings - a2.warnings);
8571
+ const hotspots = [...hotspotMap.entries()].map(([path4, warnings]) => ({ path: path4, warnings })).sort((a2, b2) => b2.warnings - a2.warnings);
8531
8572
  return {
8532
8573
  available: true,
8533
8574
  total_runs: sorted.length,
@@ -8972,7 +9013,7 @@ function serializeImpactJson(r2) {
8972
9013
  }
8973
9014
 
8974
9015
  // ../cli/src/core/savings.ts
8975
- import { parse as parseYaml7 } from "yaml";
9016
+ import { parse as parseYaml9 } from "yaml";
8976
9017
  var DEFAULT_ASSUMPTIONS = {
8977
9018
  currency: "USD",
8978
9019
  hourly_cost: 40,
@@ -8990,7 +9031,7 @@ function loadAssumptions(dir) {
8990
9031
  const p = join(dir, SAVINGS_PATH);
8991
9032
  if (!exists(p)) return { assumptions: { ...DEFAULT_ASSUMPTIONS }, source: "default" };
8992
9033
  try {
8993
- const raw = parseYaml7(readFile(p));
9034
+ const raw = parseYaml9(readFile(p));
8994
9035
  const a2 = raw?.assumptions ?? {};
8995
9036
  const t = raw?.team ?? {};
8996
9037
  const num = (v, d3) => typeof v === "number" && Number.isFinite(v) ? v : d3;
@@ -9828,7 +9869,7 @@ function getWorkItem(root, id) {
9828
9869
  const content = readText(root, item.path);
9829
9870
  return ok({ ...item, content: content ?? "" });
9830
9871
  }
9831
- function markWorkItemReady(root, id, confirm) {
9872
+ function markWorkItemReady(root, id, confirm2) {
9832
9873
  const item = listWorkItems(root).find((w3) => w3.id === id);
9833
9874
  if (!item) return fail(`Work Item "${id}" not found.`);
9834
9875
  if (item.status === "ready") {
@@ -9838,7 +9879,7 @@ function markWorkItemReady(root, id, confirm) {
9838
9879
  return fail(`Work Item ${item.id} is in state "${item.status}", not "draft". Only draft Work Items can be marked as ready.`);
9839
9880
  }
9840
9881
  const warnings = item.readiness?.warnings ?? [];
9841
- if (!confirm) {
9882
+ if (!confirm2) {
9842
9883
  return ok({
9843
9884
  status: "needs_confirmation",
9844
9885
  workItem: {
@@ -9924,8 +9965,547 @@ function listGraphHints(root, filter = {}) {
9924
9965
  });
9925
9966
  }
9926
9967
 
9927
- // ../cli/src/core/context-pack.ts
9968
+ // src/multirepo.ts
9969
+ import fs3 from "fs";
9970
+ import path3 from "path";
9928
9971
  import matter11 from "gray-matter";
9972
+ import { parse as parseYaml10, stringify as stringifyYaml4 } from "yaml";
9973
+ var ok2 = (data) => ({ ok: true, data });
9974
+ var fail2 = (message) => ({ ok: false, message });
9975
+ function moduleRepoPath(m2) {
9976
+ return m2.path ?? m2.repoPath ?? "";
9977
+ }
9978
+ function loadModulesDescriptor(root) {
9979
+ return readYaml(root, ".kaddo/modules.yml") ?? { modules: [] };
9980
+ }
9981
+ function loadConfig2(root) {
9982
+ return readYaml(root, ".kaddo/config.yml");
9983
+ }
9984
+ function projectRole2(config) {
9985
+ return config.project?.role ?? config.multirepo?.role;
9986
+ }
9987
+ function isCore2(config) {
9988
+ return projectRole2(config) === "core";
9989
+ }
9990
+ function readModuleFile(root, repoPath, relFile) {
9991
+ const moduleDir = path3.resolve(root, repoPath);
9992
+ const abs = path3.resolve(moduleDir, relFile);
9993
+ if (!abs.startsWith(moduleDir)) return null;
9994
+ if (!fs3.existsSync(abs) || !fs3.statSync(abs).isFile()) return null;
9995
+ return fs3.readFileSync(abs, "utf-8");
9996
+ }
9997
+ function detectModuleStatus2(root, repoPath) {
9998
+ const moduleDir = path3.resolve(root, repoPath);
9999
+ if (!fs3.existsSync(moduleDir)) {
10000
+ return { status: "missing", configured: false, moduleContext: false, currentState: false, codebase: false, warning: `Directory \`${repoPath}\` does not exist.` };
10001
+ }
10002
+ const configPath = path3.join(moduleDir, ".kaddo", "config.yml");
10003
+ if (!fs3.existsSync(configPath)) {
10004
+ return { status: "not_configured", configured: false, moduleContext: false, currentState: false, codebase: false, warning: `Run \`kaddo init\` inside \`${repoPath}\` and select multirepo \u2192 module.` };
10005
+ }
10006
+ let parsed = null;
10007
+ try {
10008
+ parsed = parseYaml10(fs3.readFileSync(configPath, "utf-8"));
10009
+ } catch {
10010
+ }
10011
+ const role = parsed?.project?.role ?? parsed?.multirepo?.role;
10012
+ if (role !== "module") {
10013
+ return { status: "invalid", configured: true, role, moduleContext: false, currentState: false, codebase: false, warning: `Repository has Kaddo configured but project.role is not module.` };
10014
+ }
10015
+ return {
10016
+ status: "configured",
10017
+ configured: true,
10018
+ role: "module",
10019
+ moduleContext: fs3.existsSync(path3.join(moduleDir, "knowledge", "tech", "module", "module-context.md")) || fs3.existsSync(path3.join(moduleDir, "knowledge", "module", "module-context.md")),
10020
+ currentState: fs3.existsSync(path3.join(moduleDir, "knowledge", "tech", "current-state.md")),
10021
+ codebase: fs3.existsSync(path3.join(moduleDir, "knowledge", "tech", "codebase.md"))
10022
+ };
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
+ }
10031
+ function isPlaceholder(content) {
10032
+ const body = content.replace(/^---[\s\S]*?---\n?/, "");
10033
+ return /^_[^_]+_$/m.test(body) && !body.split("\n").some((l2) => l2.trim().length > 0 && !l2.startsWith("#") && !l2.startsWith("_") && !l2.startsWith("-") && !l2.startsWith(">"));
10034
+ }
10035
+ function firstParagraph3(md) {
10036
+ const body = md.replace(/^---[\s\S]*?---\n?/, "");
10037
+ for (const block of body.split(/\n\s*\n/)) {
10038
+ const t = block.trim();
10039
+ if (t && !t.startsWith("#") && !t.startsWith(">") && !t.startsWith("_")) return t.replace(/\s+/g, " ");
10040
+ }
10041
+ return "";
10042
+ }
10043
+ function slugify2(s) {
10044
+ return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
10045
+ }
10046
+ function modulesListTool(root, args) {
10047
+ const config = loadConfig2(root);
10048
+ if (!config) return fail2("No Kaddo config found. Run `kaddo init` first.");
10049
+ if (!isCore2(config)) return fail2("kaddo_modules_list can only be called from a core repository (project.role: core).");
10050
+ const descriptor = loadModulesDescriptor(root);
10051
+ const modules = (descriptor.modules ?? []).map((m2) => {
10052
+ const rp = moduleRepoPath(m2);
10053
+ const st = detectModuleStatus2(root, rp);
10054
+ const entry = {
10055
+ id: m2.id,
10056
+ repo: rp,
10057
+ status: st.status,
10058
+ kaddo: {
10059
+ configured: st.configured,
10060
+ role: st.role,
10061
+ moduleContext: st.moduleContext,
10062
+ currentState: st.currentState,
10063
+ codebase: st.codebase
10064
+ }
10065
+ };
10066
+ if (args.includeWarnings && st.warning) entry.warning = st.warning;
10067
+ return entry;
10068
+ });
10069
+ return ok2({
10070
+ project: { name: config.project?.name ?? "unknown", structure: config.project?.structure ?? "unknown", role: "core" },
10071
+ modules
10072
+ });
10073
+ }
10074
+ function getModuleContextTool(root, args) {
10075
+ const config = loadConfig2(root);
10076
+ if (!config) return fail2("No Kaddo config found.");
10077
+ const descriptor = loadModulesDescriptor(root);
10078
+ const mod = (descriptor.modules ?? []).find((m2) => m2.id === args.module);
10079
+ if (!mod) return fail2(`Module "${args.module}" not found in .kaddo/modules.yml.`);
10080
+ const rp = moduleRepoPath(mod);
10081
+ const st = detectModuleStatus2(root, rp);
10082
+ const warnings = [];
10083
+ if (st.status !== "configured") {
10084
+ if (st.warning) warnings.push(st.warning);
10085
+ return ok2({
10086
+ module: { id: mod.id, status: st.status },
10087
+ context: null,
10088
+ warnings,
10089
+ recommendedNextStep: { label: "Initialize module repository with Kaddo.", command: "kaddo init" }
10090
+ });
10091
+ }
10092
+ const mcRelPath = resolveModuleContextPath(root, rp);
10093
+ const mcContent = readModuleFile(root, rp, mcRelPath);
10094
+ const context = {
10095
+ moduleContextPath: `${rp}/${mcRelPath}`,
10096
+ moduleContext: mcContent ?? null
10097
+ };
10098
+ if (mcContent && isPlaceholder(mcContent)) {
10099
+ warnings.push("module-context.md still contains placeholder sections.");
10100
+ }
10101
+ if (args.includeTech) {
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`;
10105
+ context.currentStateSummary = cs ? firstParagraph3(cs) : null;
10106
+ context.codebasePath = `${rp}/knowledge/tech/codebase.md`;
10107
+ context.codebaseSummary = cb ? firstParagraph3(cb) : null;
10108
+ if (!cs) warnings.push("current-state.md not found in module repository.");
10109
+ if (!cb) warnings.push("codebase.md not found in module repository.");
10110
+ }
10111
+ return ok2({
10112
+ module: { id: mod.id, repo: rp, status: "configured" },
10113
+ context,
10114
+ warnings: args.includeWarnings ? warnings : []
10115
+ });
10116
+ }
10117
+ function validateWorkItemModulesTool(root, args) {
10118
+ const items = listWorkItems(root);
10119
+ const wi = items.find((w3) => w3.id === args.workItemId);
10120
+ if (!wi) return fail2(`Work Item "${args.workItemId}" not found.`);
10121
+ const raw = readText(root, wi.path);
10122
+ if (!raw) return fail2(`Could not read Work Item file.`);
10123
+ const parsed = matter11(raw);
10124
+ const data = parsed.data;
10125
+ const affectedModules = Array.isArray(data.affected_modules) ? data.affected_modules.map(String) : [];
10126
+ 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) : [] })) : [];
10127
+ const descriptor = loadModulesDescriptor(root);
10128
+ const mapped = new Set((descriptor.modules ?? []).map((m2) => m2.id));
10129
+ const warnings = [];
10130
+ const errors = [];
10131
+ if (affectedModules.length === 0) {
10132
+ warnings.push("Work Item has no affected_modules declared.");
10133
+ }
10134
+ const moduleValidations = affectedModules.map((modId) => {
10135
+ if (!mapped.has(modId)) {
10136
+ errors.push(`Module "${modId}" is not in .kaddo/modules.yml.`);
10137
+ return { id: modId, status: "not_mapped", hasModuleContext: false, hasCurrentState: false, hasCodebase: false, hasOwnership: false };
10138
+ }
10139
+ const mod = (descriptor.modules ?? []).find((m2) => m2.id === modId);
10140
+ const rp = moduleRepoPath(mod);
10141
+ const st = detectModuleStatus2(root, rp);
10142
+ if (st.status !== "configured") {
10143
+ warnings.push(`Module "${modId}" is ${st.status}.`);
10144
+ }
10145
+ const mcRelPath = resolveModuleContextPath(root, rp);
10146
+ const mcContent = st.moduleContext ? readModuleFile(root, rp, mcRelPath) : null;
10147
+ if (mcContent && isPlaceholder(mcContent)) {
10148
+ warnings.push(`Module "${modId}" has placeholder module-context.`);
10149
+ }
10150
+ const hasOwnership = codeEntries.some((c) => c.repo && (c.repo === rp || c.repo.endsWith(`/${modId}`) || c.repo.includes(modId)));
10151
+ return { id: modId, status: st.status, hasModuleContext: st.moduleContext, hasCurrentState: st.currentState, hasCodebase: st.codebase, hasOwnership };
10152
+ });
10153
+ for (const ce of codeEntries) {
10154
+ if (ce.repo && !affectedModules.some((m2) => ce.repo.includes(m2))) {
10155
+ warnings.push(`code.repo "${ce.repo}" is not listed in affected_modules.`);
10156
+ }
10157
+ }
10158
+ for (const mv of moduleValidations) {
10159
+ if (affectedModules.includes(mv.id) && !mv.hasOwnership) {
10160
+ warnings.push(`Module "${mv.id}" is in affected_modules but has no paths in code.`);
10161
+ }
10162
+ }
10163
+ return ok2({
10164
+ workItem: { id: wi.id, title: wi.title, status: wi.status, scope: affectedModules.length > 0 ? "multirepo" : "single", affectedModules },
10165
+ validation: { valid: errors.length === 0, modules: moduleValidations, warnings, errors }
10166
+ });
10167
+ }
10168
+ function getWorkItemContextTool(root, args) {
10169
+ const config = loadConfig2(root);
10170
+ if (!config) return fail2("No Kaddo config found.");
10171
+ const items = listWorkItems(root);
10172
+ const wi = items.find((w3) => w3.id === args.workItemId);
10173
+ if (!wi) return fail2(`Work Item "${args.workItemId}" not found.`);
10174
+ const raw = readText(root, wi.path);
10175
+ if (!raw) return fail2(`Could not read Work Item file.`);
10176
+ const parsed = matter11(raw);
10177
+ const data = parsed.data;
10178
+ const affectedModules = Array.isArray(data.affected_modules) ? data.affected_modules.map(String) : [];
10179
+ 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) : [] })) : [];
10180
+ const descriptor = loadModulesDescriptor(root);
10181
+ const warnings = [];
10182
+ const modules = affectedModules.map((modId) => {
10183
+ const mod = (descriptor.modules ?? []).find((m2) => m2.id === modId);
10184
+ if (!mod) {
10185
+ warnings.push(`Module "${modId}" not in modules.yml.`);
10186
+ return { id: modId, repo: null, moduleContext: null, currentStateSummary: null, codebaseSummary: null };
10187
+ }
10188
+ const rp = moduleRepoPath(mod);
10189
+ const st = detectModuleStatus2(root, rp);
10190
+ if (st.status !== "configured") warnings.push(`Module "${modId}" is ${st.status}.`);
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 };
10196
+ });
10197
+ const ownership = { crossRepo: codeEntries.length > 1, items: codeEntries.filter((c) => c.repo).map((c) => ({ repo: c.repo, paths: c.paths ?? [] })) };
10198
+ const result = {
10199
+ project: { name: config.project?.name ?? "unknown", role: projectRole2(config) ?? "unknown", structure: config.project?.structure ?? "unknown" },
10200
+ workItem: { id: wi.id, title: wi.title, status: wi.status, affectedModules, acceptanceCriteria: extractSection(parsed.content, "Acceptance criteria"), outOfScope: extractSection(parsed.content, "Out of scope") },
10201
+ modules,
10202
+ ownership,
10203
+ warnings
10204
+ };
10205
+ if (args.includeBranchStrategy !== false && affectedModules.length > 0) {
10206
+ result.branchStrategy = buildBranchStrategy(wi, affectedModules, codeEntries, descriptor);
10207
+ }
10208
+ if (args.includeGuardExpectations) {
10209
+ result.guardExpectations = [
10210
+ "Run guard from core before closing the Work Item.",
10211
+ "Validate ownership across all affected repos."
10212
+ ];
10213
+ }
10214
+ return ok2(result);
10215
+ }
10216
+ function extractSection(content, heading) {
10217
+ const re = new RegExp(`^##\\s+${heading}\\s*$`, "im");
10218
+ const lines = content.split(/\r?\n/);
10219
+ const start = lines.findIndex((l2) => re.test(l2));
10220
+ if (start < 0) return [];
10221
+ const out = [];
10222
+ for (let i = start + 1; i < lines.length; i++) {
10223
+ if (/^##\s/.test(lines[i])) break;
10224
+ const m2 = lines[i].match(/^\s*-\s*\[[ x]?\]\s*(.+?)\s*$/);
10225
+ if (m2) out.push(m2[1]);
10226
+ }
10227
+ return out;
10228
+ }
10229
+ function buildBranchStrategy(wi, affectedModules, codeEntries, descriptor) {
10230
+ const slug4 = slugify2(wi.title);
10231
+ const wiLower = wi.id.toLowerCase();
10232
+ const coreBranch = `feature/${wiLower}-${slug4}`;
10233
+ const moduleBranches = {};
10234
+ const commitMessages = {};
10235
+ for (const modId of affectedModules) {
10236
+ const mod = (descriptor.modules ?? []).find((m2) => m2.id === modId);
10237
+ const repo = mod ? moduleRepoPath(mod) || modId : modId;
10238
+ moduleBranches[repo] = `feature/${wiLower}-${modId}-${slug4.slice(0, 20)}`;
10239
+ commitMessages[repo] = `fix(${wiLower}): update ${modId}`;
10240
+ }
10241
+ commitMessages["core"] = `docs(${wiLower}): capture cross-repo implementation summary`;
10242
+ return {
10243
+ type: affectedModules.length > 0 ? "multi-repo" : "single-repo",
10244
+ branches: { core: coreBranch, modules: moduleBranches },
10245
+ commitMessages,
10246
+ checklist: [
10247
+ "Create branches manually in each repo.",
10248
+ "Implement changes in affected module repos.",
10249
+ "Run validation per repo.",
10250
+ "Run Kaddo guard from core.",
10251
+ "Review diffs manually.",
10252
+ "Commit only with explicit human confirmation."
10253
+ ]
10254
+ };
10255
+ }
10256
+ function suggestBranchStrategyTool(root, args) {
10257
+ const items = listWorkItems(root);
10258
+ const wi = items.find((w3) => w3.id === args.workItemId);
10259
+ if (!wi) return fail2(`Work Item "${args.workItemId}" not found.`);
10260
+ const raw = readText(root, wi.path);
10261
+ if (!raw) return fail2(`Could not read Work Item file.`);
10262
+ const parsed = matter11(raw);
10263
+ const data = parsed.data;
10264
+ const affectedModules = Array.isArray(data.affected_modules) ? data.affected_modules.map(String) : [];
10265
+ 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) : [] })) : [];
10266
+ const descriptor = loadModulesDescriptor(root);
10267
+ const strategy = buildBranchStrategy(wi, affectedModules, codeEntries, descriptor);
10268
+ return ok2({
10269
+ workItem: { id: wi.id, title: wi.title },
10270
+ strategy,
10271
+ safety: { gitCommandsExecuted: false, message: "Kaddo only suggests branch and commit strategy. It does not execute git commands." }
10272
+ });
10273
+ }
10274
+ function exportCapsuleTool(root, args) {
10275
+ const config = loadConfig2(root);
10276
+ if (!config) return fail2("No Kaddo config found.");
10277
+ const role = projectRole2(config);
10278
+ const warnings = [];
10279
+ let capsuleType;
10280
+ let capsuleName;
10281
+ let capsuleData;
10282
+ if (args.module) {
10283
+ if (role === "core") {
10284
+ const descriptor = loadModulesDescriptor(root);
10285
+ const mod = (descriptor.modules ?? []).find((m2) => m2.id === args.module);
10286
+ if (!mod) return fail2(`Module "${args.module}" not found in .kaddo/modules.yml.`);
10287
+ const rp = moduleRepoPath(mod);
10288
+ const mcRelPath = resolveModuleContextPath(root, rp);
10289
+ const mc = readModuleFile(root, rp, mcRelPath);
10290
+ if (!mc) warnings.push("module-context.md not found for this module.");
10291
+ if (mc && isPlaceholder(mc)) warnings.push("module-context.md contains placeholder sections.");
10292
+ capsuleType = "module";
10293
+ capsuleName = `${slugify2(config.project?.name ?? "project")}-${args.module}`;
10294
+ capsuleData = {
10295
+ type: "knowledge-capsule",
10296
+ system: `${config.project?.name ?? "unknown"}/${args.module}`,
10297
+ version: 1,
10298
+ updated_at: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
10299
+ owner: mod.owner ?? "unknown",
10300
+ source_project: config.project?.name ?? "unknown",
10301
+ purpose: mc ? firstParagraph3(mc) : "",
10302
+ responsibilities: [],
10303
+ capabilities: mod.capabilities ?? [],
10304
+ contracts: [],
10305
+ dependencies: [],
10306
+ knownRisks: [],
10307
+ owners: mod.owner ? [mod.owner] : []
10308
+ };
10309
+ } else if (role === "module") {
10310
+ const mc = readText(root, "knowledge/tech/module/module-context.md") ?? readText(root, "knowledge/module/module-context.md");
10311
+ if (!mc) warnings.push("module-context.md not found.");
10312
+ if (mc && isPlaceholder(mc)) warnings.push("module-context.md contains placeholder sections.");
10313
+ const modId = config.module?.id ?? args.module;
10314
+ capsuleType = "module";
10315
+ capsuleName = slugify2(modId);
10316
+ capsuleData = {
10317
+ type: "knowledge-capsule",
10318
+ system: `${config.multirepo?.parent_system ?? "unknown"}/${modId}`,
10319
+ version: 1,
10320
+ updated_at: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
10321
+ purpose: mc ? firstParagraph3(mc) : "",
10322
+ responsibilities: [],
10323
+ capabilities: [],
10324
+ contracts: [],
10325
+ dependencies: [],
10326
+ knownRisks: [],
10327
+ owners: []
10328
+ };
10329
+ } else {
10330
+ return fail2("Cannot export module capsule: project role is neither core nor module.");
10331
+ }
10332
+ } else if (args.scope === "system") {
10333
+ if (role !== "core") return fail2("--scope system can only be used from a core repository.");
10334
+ const descriptor = loadModulesDescriptor(root);
10335
+ const responsibilities = [];
10336
+ for (const m2 of descriptor.modules ?? []) {
10337
+ const rp = moduleRepoPath(m2);
10338
+ const mcRelPath = resolveModuleContextPath(root, rp);
10339
+ const mc = readModuleFile(root, rp, mcRelPath);
10340
+ responsibilities.push(mc ? `[${m2.id}] ${firstParagraph3(mc) || m2.name || m2.id}` : `[${m2.id}] ${m2.name || m2.id} (no module-context)`);
10341
+ }
10342
+ capsuleType = "system";
10343
+ capsuleName = `${slugify2(config.project?.name ?? "project")}-system`;
10344
+ capsuleData = {
10345
+ type: "knowledge-capsule",
10346
+ system: config.project?.name ?? "unknown",
10347
+ version: 1,
10348
+ updated_at: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
10349
+ purpose: "",
10350
+ responsibilities,
10351
+ capabilities: [],
10352
+ contracts: [],
10353
+ dependencies: [],
10354
+ knownRisks: [],
10355
+ owners: []
10356
+ };
10357
+ } else {
10358
+ if (role === "module") {
10359
+ const mc = readText(root, "knowledge/tech/module/module-context.md") ?? readText(root, "knowledge/module/module-context.md");
10360
+ if (!mc) warnings.push("module-context.md not found.");
10361
+ if (mc && isPlaceholder(mc)) warnings.push("module-context.md contains placeholder sections.");
10362
+ const modId = config.module?.id ?? "module";
10363
+ capsuleType = "module";
10364
+ capsuleName = slugify2(modId);
10365
+ capsuleData = {
10366
+ type: "knowledge-capsule",
10367
+ system: `${config.multirepo?.parent_system ?? "unknown"}/${modId}`,
10368
+ version: 1,
10369
+ updated_at: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
10370
+ purpose: mc ? firstParagraph3(mc) : "",
10371
+ responsibilities: [],
10372
+ capabilities: [],
10373
+ contracts: [],
10374
+ dependencies: [],
10375
+ knownRisks: [],
10376
+ owners: []
10377
+ };
10378
+ } else {
10379
+ capsuleType = "project";
10380
+ capsuleName = slugify2(config.project?.name ?? "project");
10381
+ capsuleData = {
10382
+ type: "knowledge-capsule",
10383
+ system: config.project?.name ?? "unknown",
10384
+ version: 1,
10385
+ updated_at: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
10386
+ purpose: "",
10387
+ responsibilities: [],
10388
+ capabilities: [],
10389
+ contracts: [],
10390
+ dependencies: [],
10391
+ knownRisks: [],
10392
+ owners: []
10393
+ };
10394
+ }
10395
+ }
10396
+ const jsonPath = `.kaddo/exports/${capsuleName}.capsule.json`;
10397
+ const mdPath = `.kaddo/exports/${capsuleName}.capsule.md`;
10398
+ const jsonContent = JSON.stringify(capsuleData, null, 2) + "\n";
10399
+ const mdContent = renderCapsuleMd(capsuleData);
10400
+ writeDerived(root, jsonPath, jsonContent);
10401
+ writeDerived(root, mdPath, mdContent);
10402
+ return ok2({ exported: true, type: capsuleType, module: args.module ?? void 0, paths: { json: jsonPath, md: mdPath }, warnings });
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
+ }
10468
+ function renderCapsuleMd(data) {
10469
+ const fm = ["---", `type: knowledge-capsule`, `system: ${data.system}`, `version: ${data.version}`, `updated_at: ${data.updated_at}`, "---", ""].join("\n");
10470
+ const list2 = (items) => items.length === 0 ? "_To be completed._" : items.map((i) => `- ${i}`).join("\n");
10471
+ return [
10472
+ fm,
10473
+ `# ${data.system} \u2014 Knowledge Capsule
10474
+ `,
10475
+ `## Purpose
10476
+
10477
+ ${data.purpose || "_To be completed._"}
10478
+ `,
10479
+ `## Responsibilities
10480
+
10481
+ ${list2(data.responsibilities)}
10482
+ `,
10483
+ `## Capabilities
10484
+
10485
+ ${list2(data.capabilities)}
10486
+ `,
10487
+ `## Contracts
10488
+
10489
+ ${list2(data.contracts)}
10490
+ `,
10491
+ `## Dependencies
10492
+
10493
+ ${list2(data.dependencies)}
10494
+ `,
10495
+ `## Known Risks
10496
+
10497
+ ${list2(data.knownRisks)}
10498
+ `,
10499
+ `## Owners
10500
+
10501
+ ${list2(data.owners)}
10502
+ `,
10503
+ "> Security: this capsule must never contain secrets, tokens, credentials, PII or source code.\n"
10504
+ ].join("\n");
10505
+ }
10506
+
10507
+ // ../cli/src/core/context-pack.ts
10508
+ import matter12 from "gray-matter";
9929
10509
  var CONTEXT_PACK_VERSION = "1";
9930
10510
  var ARCH_DIR3 = "knowledge";
9931
10511
  function readScanJson(dir) {
@@ -9937,8 +10517,8 @@ function readScanJson(dir) {
9937
10517
  return null;
9938
10518
  }
9939
10519
  }
9940
- function firstParagraph3(markdown) {
9941
- const body = matter11(markdown).content.trim();
10520
+ function firstParagraph4(markdown) {
10521
+ const body = matter12(markdown).content.trim();
9942
10522
  const para = body.split("\n\n").map((p) => p.trim()).find((p) => p && !p.startsWith("#"));
9943
10523
  return para ?? "";
9944
10524
  }
@@ -9946,7 +10526,7 @@ function readMarkdownSummary(dir, file) {
9946
10526
  const p = join(dir, ARCH_DIR3, file);
9947
10527
  if (!exists(p)) return null;
9948
10528
  try {
9949
- return firstParagraph3(readFile(p));
10529
+ return firstParagraph4(readFile(p));
9950
10530
  } catch {
9951
10531
  return null;
9952
10532
  }
@@ -11112,6 +11692,84 @@ function createServer(root) {
11112
11692
  { title: "Get skill", description: "Get an installed reusable skill by id.", inputSchema: { id: z3.string() } },
11113
11693
  async (args) => toolText(guarded(root, () => getSkillTool(root, args.id)))
11114
11694
  );
11695
+ server.registerTool(
11696
+ "kaddo_modules_list",
11697
+ {
11698
+ title: "List multirepo modules",
11699
+ description: "List mapped modules and their Kaddo configuration status. Only works from a core repository.",
11700
+ inputSchema: { includeWarnings: z3.boolean().optional() }
11701
+ },
11702
+ async (args) => toolText(guarded(root, () => modulesListTool(root, args)))
11703
+ );
11704
+ server.registerTool(
11705
+ "kaddo_get_module_context",
11706
+ {
11707
+ title: "Get module context",
11708
+ description: "Get the local knowledge context (module-context.md, tech files) for a mapped module.",
11709
+ inputSchema: {
11710
+ module: z3.string(),
11711
+ includeTech: z3.boolean().optional(),
11712
+ includeWarnings: z3.boolean().optional()
11713
+ }
11714
+ },
11715
+ async (args) => toolText(guarded(root, () => getModuleContextTool(root, args)))
11716
+ );
11717
+ server.registerTool(
11718
+ "kaddo_validate_work_item_modules",
11719
+ {
11720
+ title: "Validate Work Item modules",
11721
+ description: "Validate that a Work Item has coherent affected_modules and sufficient cross-repo ownership.",
11722
+ inputSchema: { workItemId: z3.string() }
11723
+ },
11724
+ async (args) => toolText(guarded(root, () => validateWorkItemModulesTool(root, args)))
11725
+ );
11726
+ server.registerTool(
11727
+ "kaddo_get_work_item_context",
11728
+ {
11729
+ title: "Get Work Item context",
11730
+ description: "Composite context for implementing a multirepo Work Item: WI details, affected module contexts, ownership, branch strategy.",
11731
+ inputSchema: {
11732
+ workItemId: z3.string(),
11733
+ includeModuleContexts: z3.boolean().optional(),
11734
+ includeBranchStrategy: z3.boolean().optional(),
11735
+ includeGuardExpectations: z3.boolean().optional()
11736
+ }
11737
+ },
11738
+ async (args) => toolText(guarded(root, () => getWorkItemContextTool(root, args)))
11739
+ );
11740
+ server.registerTool(
11741
+ "kaddo_suggest_branch_strategy",
11742
+ {
11743
+ title: "Suggest branch strategy",
11744
+ description: "Suggest branch names, commit messages, and a checklist for a multirepo Work Item. Does NOT execute git.",
11745
+ inputSchema: { workItemId: z3.string() }
11746
+ },
11747
+ async (args) => toolText(guarded(root, () => suggestBranchStrategyTool(root, args)))
11748
+ );
11749
+ server.registerTool(
11750
+ "kaddo_export_capsule",
11751
+ {
11752
+ title: "Export Knowledge Capsule",
11753
+ description: "Export a Knowledge Capsule as a derived artifact under .kaddo/exports/. Supports scope=system (from core) and module=<id> (from core or module).",
11754
+ inputSchema: {
11755
+ scope: z3.string().optional(),
11756
+ module: z3.string().optional()
11757
+ }
11758
+ },
11759
+ async (args) => toolText(guarded(root, () => exportCapsuleTool(root, args)))
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
+ );
11115
11773
  let installedSkills = [];
11116
11774
  try {
11117
11775
  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.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": {