@kaddo/mcp 3.62.0 → 3.64.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 +320 -63
  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";
@@ -685,6 +693,34 @@ function firstUnrefinedLayerAgent(layers) {
685
693
  return { agent: "architecture-agent", step: "Use architecture-agent to complete knowledge/tech/current-state.md" };
686
694
  }
687
695
  function assessPhase(input) {
696
+ if (input.isModuleRepo) {
697
+ const techStatus = layer(input.layers, "Tech");
698
+ const techReady = !NOT_READY_LAYER.includes(techStatus);
699
+ if (techReady) {
700
+ return {
701
+ phase: "Active Delivery",
702
+ reasons: ["Module repo \u2014 Tech layer ready", "Business/Product managed by core"],
703
+ recommendedAgents: [],
704
+ nextStep: "Module knowledge is ready for core orchestration.",
705
+ llmInstructions: [
706
+ "This is a module repo. Business, Product, and Delivery are managed by the core repo.",
707
+ "Do not create business, product, roadmap or Work Items here."
708
+ ]
709
+ };
710
+ }
711
+ return {
712
+ phase: "Knowledge Refinement",
713
+ reasons: ["Module repo \u2014 Tech layer incomplete", "Business/Product managed by core"],
714
+ recommendedAgents: ["module-context-agent"],
715
+ nextStep: "Use module-context-agent to refine knowledge/tech/module/module-context.md",
716
+ llmInstructions: [
717
+ "Use the module-context-agent to refine the module context.",
718
+ "Do not create business, product, roadmap or Work Items in this module.",
719
+ "Do not install agents or skills.",
720
+ "Do not write application code."
721
+ ]
722
+ };
723
+ }
688
724
  const phase = determinePhase(input);
689
725
  const reasons = buildReasons(input);
690
726
  const bs = input.workItems.byState;
@@ -790,7 +826,7 @@ function assessPhase(input) {
790
826
 
791
827
  // ../cli/src/core/capsule.ts
792
828
  import matter4 from "gray-matter";
793
- import { parse as parseYaml6, stringify as stringifyYaml2 } from "yaml";
829
+ import { parse as parseYaml7, stringify as stringifyYaml3 } from "yaml";
794
830
 
795
831
  // ../cli/src/services/owners.ts
796
832
  import { parse as parseYaml3 } from "yaml";
@@ -847,7 +883,7 @@ function loadMappedModules(dir) {
847
883
  modules.push({
848
884
  id,
849
885
  name: typeof m2.name === "string" ? m2.name : void 0,
850
- repoPath: typeof m2.repoPath === "string" ? m2.repoPath : "",
886
+ repoPath: typeof m2.repoPath === "string" ? m2.repoPath : typeof m2.path === "string" ? m2.path : "",
851
887
  type: typeof m2.type === "string" ? m2.type : void 0,
852
888
  mainTechnology: typeof m2.mainTechnology === "string" ? m2.mainTechnology : void 0,
853
889
  owner: typeof m2.owner === "string" ? m2.owner : void 0,
@@ -896,13 +932,15 @@ function detectModuleStatus(coreDir, repoPath) {
896
932
  status: "configured",
897
933
  configured: true,
898
934
  role: "module",
899
- moduleContext: exists(join(moduleDir, "knowledge", "module", "module-context.md")),
935
+ moduleContext: exists(join(moduleDir, "knowledge", "tech", "module", "module-context.md")) || exists(join(moduleDir, "knowledge", "module", "module-context.md")),
900
936
  currentState: exists(join(moduleDir, "knowledge", "tech", "current-state.md")),
901
937
  codebase: exists(join(moduleDir, "knowledge", "tech", "codebase.md"))
902
938
  };
903
939
  }
904
940
  function readModuleContext(coreDir, repoPath) {
905
- const p = join(coreDir, repoPath, "knowledge", "module", "module-context.md");
941
+ const newPath = join(coreDir, repoPath, "knowledge", "tech", "module", "module-context.md");
942
+ const legacyPath = join(coreDir, repoPath, "knowledge", "module", "module-context.md");
943
+ const p = exists(newPath) ? newPath : legacyPath;
906
944
  if (!exists(p)) return null;
907
945
  try {
908
946
  return readFile(p);
@@ -1042,7 +1080,7 @@ var Q2 = o("\u25B2", "!");
1042
1080
  var ee = o("\u25A0", "x");
1043
1081
 
1044
1082
  // ../cli/src/commands/modules-map.ts
1045
- import { parse as parseYaml5, stringify as stringifyYaml } from "yaml";
1083
+ import { parse as parseYaml6, stringify as stringifyYaml2 } from "yaml";
1046
1084
 
1047
1085
  // ../cli/src/templates/registry.ts
1048
1086
  var QUALITY = "## Quality checklist";
@@ -2153,6 +2191,9 @@ ${QUALITY}
2153
2191
  - [ ] The next step is clear.
2154
2192
  `;
2155
2193
 
2194
+ // ../cli/src/commands/modules-discover.ts
2195
+ import { parse as parseYaml5, stringify as stringifyYaml } from "yaml";
2196
+
2156
2197
  // ../cli/src/core/capsule.ts
2157
2198
  var KNOWLEDGE2 = "knowledge";
2158
2199
  function today() {
@@ -2252,7 +2293,7 @@ function loadExternalRegistry(dir) {
2252
2293
  const p = join(dir, EXTERNAL_PATH);
2253
2294
  if (!exists(p)) return [];
2254
2295
  try {
2255
- const parsed = parseYaml6(readFile(p));
2296
+ const parsed = parseYaml7(readFile(p));
2256
2297
  return Array.isArray(parsed?.external) ? parsed.external : [];
2257
2298
  } catch {
2258
2299
  return [];
@@ -2996,6 +3037,21 @@ function layerQuality(dir, layer2) {
2996
3037
  return worst;
2997
3038
  }
2998
3039
  function knowledgeLayers(dir) {
3040
+ const config = loadConfig(dir);
3041
+ if (config && isModule(config)) {
3042
+ return [
3043
+ { layer: "Business", status: "Not applicable", detected: [] },
3044
+ { layer: "Product", status: "Not applicable", detected: [] },
3045
+ ...discoverLayers(dir).filter((l2) => l2.layer === "Tech").map((l2) => {
3046
+ if (l2.status !== "Consolidated" && l2.status !== "Structured") return l2;
3047
+ const q3 = layerQuality(dir, l2.layer);
3048
+ if (q3 === "placeholder") return { ...l2, status: "Placeholder" };
3049
+ if (q3 === "weak") return { ...l2, status: "Weak" };
3050
+ return l2;
3051
+ }),
3052
+ { layer: "Delivery", status: "Managed by core", detected: [] }
3053
+ ];
3054
+ }
2999
3055
  return discoverLayers(dir).map((l2) => {
3000
3056
  if (l2.status !== "Consolidated" && l2.status !== "Structured") return l2;
3001
3057
  const q3 = layerQuality(dir, l2.layer);
@@ -3693,6 +3749,9 @@ function buildAdapterStatuses(dir) {
3693
3749
  });
3694
3750
  }
3695
3751
 
3752
+ // ../cli/src/core/next-step.ts
3753
+ import { parse as parseYaml8 } from "yaml";
3754
+
3696
3755
  // ../cli/src/agents/groups.ts
3697
3756
  var AGENT_GROUPS = {
3698
3757
  business: ["business-agent.md"],
@@ -3845,6 +3904,14 @@ function buildDeliveryState(dir) {
3845
3904
  adapters_installed: adapters.length
3846
3905
  };
3847
3906
  }
3907
+ function hasRegisteredModules(modulesYmlPath) {
3908
+ try {
3909
+ const parsed = parseYaml8(readFile(modulesYmlPath));
3910
+ return Array.isArray(parsed?.modules) && parsed.modules.length > 0;
3911
+ } catch {
3912
+ return false;
3913
+ }
3914
+ }
3848
3915
  function safeRead(p) {
3849
3916
  try {
3850
3917
  return readFile(p);
@@ -3894,12 +3961,20 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
3894
3961
  }
3895
3962
  const state = config.project.state;
3896
3963
  const q3 = (rel) => analyzeKnowledgeArtifact(dir, rel);
3964
+ const resolveAgent = (agent) => {
3965
+ const file = agent.endsWith(".md") ? agent : `${agent}.md`;
3966
+ const path4 = agentInstallPath(file);
3967
+ const installed = isFile(join(dir, path4));
3968
+ const group = agentGroupOf(file);
3969
+ return { agentPath: path4, agentInstalled: installed, installCommand: installed ? void 0 : `kaddo add agents --group ${group}` };
3970
+ };
3897
3971
  const moduleRepo = isModule(config);
3898
- const MC = "knowledge/module/module-context.md";
3972
+ const coreRepo = isCore(config);
3973
+ const MC = exists(join(dir, "knowledge/tech/module/module-context.md")) ? "knowledge/tech/module/module-context.md" : "knowledge/module/module-context.md";
3899
3974
  if (moduleRepo) {
3900
3975
  const qMC = q3(MC);
3901
3976
  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." };
3977
+ 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
3978
  }
3904
3979
  if ((state === "pre-ai" || state === "legacy") && !exists(join(dir, ".kaddo", "scan.json"))) {
3905
3980
  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." };
@@ -3908,15 +3983,26 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
3908
3983
  return { id: "context", phase: "Discovery", label: "Run `kaddo context` to prepare the LLM context pack.", command: "kaddo context", reason: "No context pack has been generated yet." };
3909
3984
  }
3910
3985
  if (qMC !== "useful") {
3986
+ const ar = resolveAgent("module-context-agent");
3911
3987
  return {
3912
3988
  id: "refine-module-context",
3913
3989
  phase: "Knowledge Refinement",
3914
- label: "Use module-context-agent to refine `knowledge/module/module-context.md`.",
3990
+ label: "Use module-context-agent to refine `knowledge/tech/module/module-context.md`.",
3915
3991
  agent: "module-context-agent",
3916
3992
  skill: "module-context-refinement",
3917
3993
  target: MC,
3994
+ agentPath: ar.agentPath,
3995
+ agentInstalled: ar.agentInstalled,
3996
+ installCommand: ar.installCommand,
3918
3997
  reason: "Module context is still a placeholder.",
3919
- instructions: ["Refine the module context with real knowledge about this module.", "Do not create business.md or product.md.", "Do not write code."]
3998
+ instructions: [
3999
+ "Refine the module context using evidence from this repository.",
4000
+ "Use the parent system context when available.",
4001
+ "Do not create business, product, roadmap or Work Items in this module.",
4002
+ "Do not install agents or skills in this module.",
4003
+ "Do not write application code.",
4004
+ "Preserve frontmatter and record unknowns as open questions."
4005
+ ]
3920
4006
  };
3921
4007
  }
3922
4008
  const qCurrentState2 = q3(CS), qCodebase2 = q3(CB);
@@ -3945,12 +4031,18 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
3945
4031
  if (!exists(join(dir, ".kaddo", "understand.md"))) {
3946
4032
  return { id: "understand", phase: "Discovery", label: "Run `kaddo understand` to summarize the project context.", command: "kaddo understand", reason: "No understand handoff has been generated yet." };
3947
4033
  }
3948
- 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." };
4034
+ return { id: "module-ready", phase: "Ready for Core Orchestration", label: "Module knowledge is complete. Create and manage Work Items from the core repository.", reason: "This module's knowledge is ready for core orchestration. Work Items are created and tracked in the core." };
3949
4035
  }
3950
4036
  const qBusiness = q3(B2), qProduct = q3(P2), qCap = q3(CAP), qCurrentState = q3(CS), qCodebase = q3(CB);
3951
- if (qBusiness === "missing" || qProduct === "missing") {
4037
+ if (!coreRepo && (qBusiness === "missing" || qProduct === "missing")) {
3952
4038
  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
4039
  }
4040
+ if (coreRepo) {
4041
+ const modulesYmlPath = join(dir, ".kaddo", "modules.yml");
4042
+ if (!exists(modulesYmlPath) || !hasRegisteredModules(modulesYmlPath)) {
4043
+ 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." };
4044
+ }
4045
+ }
3954
4046
  const ctx = buildCodexAdapterContext(dir);
3955
4047
  if (!ctx.hasAgents) {
3956
4048
  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." };
@@ -3965,13 +4057,6 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
3965
4057
  return { id: "context", phase: "Discovery", label: "Run `kaddo context` to prepare the LLM context pack.", command: "kaddo context", reason: "No context pack has been generated yet." };
3966
4058
  }
3967
4059
  const discovery = state === "pre-ai" || state === "legacy";
3968
- const resolveAgent = (agent) => {
3969
- const file = agent.endsWith(".md") ? agent : `${agent}.md`;
3970
- const path4 = agentInstallPath(file);
3971
- const installed = isFile(join(dir, path4));
3972
- const group = agentGroupOf(file);
3973
- return { agentPath: path4, agentInstalled: installed, installCommand: installed ? void 0 : `kaddo add agents --group ${group}` };
3974
- };
3975
4060
  const refine = (id, agent, target, quality, verb = "complete") => {
3976
4061
  const ar = resolveAgent(agent);
3977
4062
  return {
@@ -7571,6 +7656,67 @@ var identifyRisks = {
7571
7656
  return { status: hasLegacy ? "done" : "pending", agent: hasLegacy ? void 0 : "legacy-agent" };
7572
7657
  }
7573
7658
  };
7659
+ var refineModuleContext = {
7660
+ id: "refine-module-context",
7661
+ label: "Refine module context",
7662
+ evaluate: (ctx) => {
7663
+ const newPath = join(ctx.dir, "knowledge/tech/module/module-context.md");
7664
+ const legacyPath = join(ctx.dir, "knowledge/module/module-context.md");
7665
+ const mcPath = exists(newPath) ? "knowledge/tech/module/module-context.md" : exists(legacyPath) ? "knowledge/module/module-context.md" : null;
7666
+ if (!mcPath) return { status: "pending", agent: "module-context-agent", skill: "module-context-refinement" };
7667
+ const q3 = analyzeKnowledgeArtifact(ctx.dir, mcPath);
7668
+ if (q3 === "useful") return { status: "done", evidence: [mcPath] };
7669
+ return { status: "current", agent: "module-context-agent", skill: "module-context-refinement", evidence: [mcPath], reason: "Module context is still a placeholder." };
7670
+ }
7671
+ };
7672
+ var describeModuleCurrentState = {
7673
+ id: "describe-current-state",
7674
+ label: "Describe current state",
7675
+ evaluate: (ctx) => ({
7676
+ status: isUseful(ctx.qCurrentState) ? "done" : "pending",
7677
+ evidence: isUseful(ctx.qCurrentState) ? ["knowledge/tech/current-state.md"] : void 0,
7678
+ agent: isUseful(ctx.qCurrentState) ? void 0 : "architecture-agent"
7679
+ })
7680
+ };
7681
+ var mapModuleCodebase = {
7682
+ id: "map-codebase",
7683
+ label: "Map codebase",
7684
+ evaluate: (ctx) => ({
7685
+ status: isUseful(ctx.qCodebase) ? "done" : "pending",
7686
+ evidence: isUseful(ctx.qCodebase) ? ["knowledge/tech/codebase.md"] : void 0,
7687
+ agent: isUseful(ctx.qCodebase) ? void 0 : "architecture-agent"
7688
+ })
7689
+ };
7690
+ var validateModuleKnowledge = {
7691
+ id: "validate-module-knowledge",
7692
+ label: "Validate module knowledge",
7693
+ evaluate: (ctx) => {
7694
+ const hasUnderstand = exists(join(ctx.dir, ".kaddo/understand.md"));
7695
+ if (hasUnderstand) return { status: "done", evidence: [".kaddo/understand.md"] };
7696
+ return { status: "pending", command: "kaddo understand" };
7697
+ }
7698
+ };
7699
+ var readyForCoreOrchestration = {
7700
+ id: "ready-for-core-orchestration",
7701
+ label: "Ready for core orchestration",
7702
+ evaluate: (ctx) => {
7703
+ const newPath = join(ctx.dir, "knowledge/tech/module/module-context.md");
7704
+ const legacyPath = join(ctx.dir, "knowledge/module/module-context.md");
7705
+ const mcPath = exists(newPath) ? "knowledge/tech/module/module-context.md" : exists(legacyPath) ? "knowledge/module/module-context.md" : null;
7706
+ const mcUseful = mcPath ? analyzeKnowledgeArtifact(ctx.dir, mcPath) === "useful" : false;
7707
+ const allDone = mcUseful && isUseful(ctx.qCurrentState) && isUseful(ctx.qCodebase);
7708
+ return { status: allDone ? "done" : "pending", reason: allDone ? void 0 : "Module knowledge is not yet complete." };
7709
+ }
7710
+ };
7711
+ var MODULE_STEPS = [
7712
+ enableKaddo,
7713
+ scanRepository,
7714
+ refineModuleContext,
7715
+ describeModuleCurrentState,
7716
+ mapModuleCodebase,
7717
+ validateModuleKnowledge,
7718
+ readyForCoreOrchestration
7719
+ ];
7574
7720
  var NEW_STEPS = [
7575
7721
  enableKaddo,
7576
7722
  defineBusiness,
@@ -7711,17 +7857,18 @@ function mapNextStepId(id) {
7711
7857
  "review-work-item": "refine-work-item",
7712
7858
  "prepare-implementation": "prepare-implementation",
7713
7859
  "implement-work-item": "prepare-implementation",
7714
- "refine-module-context": "refine-work-item",
7860
+ "refine-module-context": "refine-module-context",
7715
7861
  "init-module-context": "enable-kaddo",
7716
- "module-ready": "prepare-implementation",
7717
- "validate-work-item-modules": "prepare-implementation"
7862
+ "module-ready": "ready-for-core-orchestration",
7863
+ "validate-work-item-modules": "validate-module-knowledge",
7864
+ "modules-discover": "enable-kaddo"
7718
7865
  };
7719
7866
  return MAP[id] ?? id;
7720
7867
  }
7721
7868
  function buildProjectRoute(dir) {
7722
7869
  const config = loadConfig(dir);
7723
7870
  const state = config?.project.state ?? "pre-ai";
7724
- const defs = stepsForState(state);
7871
+ const defs = config && isModule(config) ? MODULE_STEPS : stepsForState(state);
7725
7872
  const ctx = buildRouteContext(dir);
7726
7873
  const steps = defs.map((d3) => {
7727
7874
  const result = d3.evaluate(ctx);
@@ -8108,7 +8255,8 @@ function buildProjectExplanation(dir) {
8108
8255
  roadmapQuality: buildRoadmapQuality(dir),
8109
8256
  projectRoute: buildProjectRoute(dir),
8110
8257
  scanSignals: loadScanSignals(dir),
8111
- metadataHealth: analyzeMetadataHealth(dir)
8258
+ metadataHealth: analyzeMetadataHealth(dir),
8259
+ isModuleRepo: config != null && isModule(config)
8112
8260
  };
8113
8261
  }
8114
8262
  function stateLabel(state) {
@@ -8981,7 +9129,7 @@ function serializeImpactJson(r2) {
8981
9129
  }
8982
9130
 
8983
9131
  // ../cli/src/core/savings.ts
8984
- import { parse as parseYaml7 } from "yaml";
9132
+ import { parse as parseYaml9 } from "yaml";
8985
9133
  var DEFAULT_ASSUMPTIONS = {
8986
9134
  currency: "USD",
8987
9135
  hourly_cost: 40,
@@ -8999,7 +9147,7 @@ function loadAssumptions(dir) {
8999
9147
  const p = join(dir, SAVINGS_PATH);
9000
9148
  if (!exists(p)) return { assumptions: { ...DEFAULT_ASSUMPTIONS }, source: "default" };
9001
9149
  try {
9002
- const raw = parseYaml7(readFile(p));
9150
+ const raw = parseYaml9(readFile(p));
9003
9151
  const a2 = raw?.assumptions ?? {};
9004
9152
  const t = raw?.team ?? {};
9005
9153
  const num = (v, d3) => typeof v === "number" && Number.isFinite(v) ? v : d3;
@@ -9837,7 +9985,7 @@ function getWorkItem(root, id) {
9837
9985
  const content = readText(root, item.path);
9838
9986
  return ok({ ...item, content: content ?? "" });
9839
9987
  }
9840
- function markWorkItemReady(root, id, confirm) {
9988
+ function markWorkItemReady(root, id, confirm2) {
9841
9989
  const item = listWorkItems(root).find((w3) => w3.id === id);
9842
9990
  if (!item) return fail(`Work Item "${id}" not found.`);
9843
9991
  if (item.status === "ready") {
@@ -9847,7 +9995,7 @@ function markWorkItemReady(root, id, confirm) {
9847
9995
  return fail(`Work Item ${item.id} is in state "${item.status}", not "draft". Only draft Work Items can be marked as ready.`);
9848
9996
  }
9849
9997
  const warnings = item.readiness?.warnings ?? [];
9850
- if (!confirm) {
9998
+ if (!confirm2) {
9851
9999
  return ok({
9852
10000
  status: "needs_confirmation",
9853
10001
  workItem: {
@@ -9937,9 +10085,12 @@ function listGraphHints(root, filter = {}) {
9937
10085
  import fs3 from "fs";
9938
10086
  import path3 from "path";
9939
10087
  import matter11 from "gray-matter";
9940
- import { parse as parseYaml8 } from "yaml";
10088
+ import { parse as parseYaml10, stringify as stringifyYaml4 } from "yaml";
9941
10089
  var ok2 = (data) => ({ ok: true, data });
9942
10090
  var fail2 = (message) => ({ ok: false, message });
10091
+ function moduleRepoPath(m2) {
10092
+ return m2.path ?? m2.repoPath ?? "";
10093
+ }
9943
10094
  function loadModulesDescriptor(root) {
9944
10095
  return readYaml(root, ".kaddo/modules.yml") ?? { modules: [] };
9945
10096
  }
@@ -9949,7 +10100,7 @@ function loadConfig2(root) {
9949
10100
  function projectRole2(config) {
9950
10101
  return config.project?.role ?? config.multirepo?.role;
9951
10102
  }
9952
- function isCore(config) {
10103
+ function isCore2(config) {
9953
10104
  return projectRole2(config) === "core";
9954
10105
  }
9955
10106
  function readModuleFile(root, repoPath, relFile) {
@@ -9970,7 +10121,7 @@ function detectModuleStatus2(root, repoPath) {
9970
10121
  }
9971
10122
  let parsed = null;
9972
10123
  try {
9973
- parsed = parseYaml8(fs3.readFileSync(configPath, "utf-8"));
10124
+ parsed = parseYaml10(fs3.readFileSync(configPath, "utf-8"));
9974
10125
  } catch {
9975
10126
  }
9976
10127
  const role = parsed?.project?.role ?? parsed?.multirepo?.role;
@@ -9981,11 +10132,18 @@ function detectModuleStatus2(root, repoPath) {
9981
10132
  status: "configured",
9982
10133
  configured: true,
9983
10134
  role: "module",
9984
- moduleContext: fs3.existsSync(path3.join(moduleDir, "knowledge", "module", "module-context.md")),
10135
+ moduleContext: fs3.existsSync(path3.join(moduleDir, "knowledge", "tech", "module", "module-context.md")) || fs3.existsSync(path3.join(moduleDir, "knowledge", "module", "module-context.md")),
9985
10136
  currentState: fs3.existsSync(path3.join(moduleDir, "knowledge", "tech", "current-state.md")),
9986
10137
  codebase: fs3.existsSync(path3.join(moduleDir, "knowledge", "tech", "codebase.md"))
9987
10138
  };
9988
10139
  }
10140
+ function resolveModuleContextPath(root, repoPath) {
10141
+ const moduleDir = path3.resolve(root, repoPath);
10142
+ if (fs3.existsSync(path3.join(moduleDir, "knowledge", "tech", "module", "module-context.md"))) {
10143
+ return "knowledge/tech/module/module-context.md";
10144
+ }
10145
+ return "knowledge/module/module-context.md";
10146
+ }
9989
10147
  function isPlaceholder(content) {
9990
10148
  const body = content.replace(/^---[\s\S]*?---\n?/, "");
9991
10149
  return /^_[^_]+_$/m.test(body) && !body.split("\n").some((l2) => l2.trim().length > 0 && !l2.startsWith("#") && !l2.startsWith("_") && !l2.startsWith("-") && !l2.startsWith(">"));
@@ -10004,13 +10162,14 @@ function slugify2(s) {
10004
10162
  function modulesListTool(root, args) {
10005
10163
  const config = loadConfig2(root);
10006
10164
  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).");
10165
+ if (!isCore2(config)) return fail2("kaddo_modules_list can only be called from a core repository (project.role: core).");
10008
10166
  const descriptor = loadModulesDescriptor(root);
10009
10167
  const modules = (descriptor.modules ?? []).map((m2) => {
10010
- const st = detectModuleStatus2(root, m2.repoPath);
10168
+ const rp = moduleRepoPath(m2);
10169
+ const st = detectModuleStatus2(root, rp);
10011
10170
  const entry = {
10012
10171
  id: m2.id,
10013
- repo: m2.repoPath,
10172
+ repo: rp,
10014
10173
  status: st.status,
10015
10174
  kaddo: {
10016
10175
  configured: st.configured,
@@ -10034,7 +10193,8 @@ function getModuleContextTool(root, args) {
10034
10193
  const descriptor = loadModulesDescriptor(root);
10035
10194
  const mod = (descriptor.modules ?? []).find((m2) => m2.id === args.module);
10036
10195
  if (!mod) return fail2(`Module "${args.module}" not found in .kaddo/modules.yml.`);
10037
- const st = detectModuleStatus2(root, mod.repoPath);
10196
+ const rp = moduleRepoPath(mod);
10197
+ const st = detectModuleStatus2(root, rp);
10038
10198
  const warnings = [];
10039
10199
  if (st.status !== "configured") {
10040
10200
  if (st.warning) warnings.push(st.warning);
@@ -10045,26 +10205,27 @@ function getModuleContextTool(root, args) {
10045
10205
  recommendedNextStep: { label: "Initialize module repository with Kaddo.", command: "kaddo init" }
10046
10206
  });
10047
10207
  }
10048
- const mcContent = readModuleFile(root, mod.repoPath, "knowledge/module/module-context.md");
10208
+ const mcRelPath = resolveModuleContextPath(root, rp);
10209
+ const mcContent = readModuleFile(root, rp, mcRelPath);
10049
10210
  const context = {
10050
- moduleContextPath: `${mod.repoPath}/knowledge/module/module-context.md`,
10211
+ moduleContextPath: `${rp}/${mcRelPath}`,
10051
10212
  moduleContext: mcContent ?? null
10052
10213
  };
10053
10214
  if (mcContent && isPlaceholder(mcContent)) {
10054
10215
  warnings.push("module-context.md still contains placeholder sections.");
10055
10216
  }
10056
10217
  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`;
10218
+ const cs = readModuleFile(root, rp, "knowledge/tech/current-state.md");
10219
+ const cb = readModuleFile(root, rp, "knowledge/tech/codebase.md");
10220
+ context.currentStatePath = `${rp}/knowledge/tech/current-state.md`;
10060
10221
  context.currentStateSummary = cs ? firstParagraph3(cs) : null;
10061
- context.codebasePath = `${mod.repoPath}/knowledge/tech/codebase.md`;
10222
+ context.codebasePath = `${rp}/knowledge/tech/codebase.md`;
10062
10223
  context.codebaseSummary = cb ? firstParagraph3(cb) : null;
10063
10224
  if (!cs) warnings.push("current-state.md not found in module repository.");
10064
10225
  if (!cb) warnings.push("codebase.md not found in module repository.");
10065
10226
  }
10066
10227
  return ok2({
10067
- module: { id: mod.id, repo: mod.repoPath, status: "configured" },
10228
+ module: { id: mod.id, repo: rp, status: "configured" },
10068
10229
  context,
10069
10230
  warnings: args.includeWarnings ? warnings : []
10070
10231
  });
@@ -10092,15 +10253,17 @@ function validateWorkItemModulesTool(root, args) {
10092
10253
  return { id: modId, status: "not_mapped", hasModuleContext: false, hasCurrentState: false, hasCodebase: false, hasOwnership: false };
10093
10254
  }
10094
10255
  const mod = (descriptor.modules ?? []).find((m2) => m2.id === modId);
10095
- const st = detectModuleStatus2(root, mod.repoPath);
10256
+ const rp = moduleRepoPath(mod);
10257
+ const st = detectModuleStatus2(root, rp);
10096
10258
  if (st.status !== "configured") {
10097
10259
  warnings.push(`Module "${modId}" is ${st.status}.`);
10098
10260
  }
10099
- const mcContent = st.moduleContext ? readModuleFile(root, mod.repoPath, "knowledge/module/module-context.md") : null;
10261
+ const mcRelPath = resolveModuleContextPath(root, rp);
10262
+ const mcContent = st.moduleContext ? readModuleFile(root, rp, mcRelPath) : null;
10100
10263
  if (mcContent && isPlaceholder(mcContent)) {
10101
10264
  warnings.push(`Module "${modId}" has placeholder module-context.`);
10102
10265
  }
10103
- const hasOwnership = codeEntries.some((c) => c.repo && (c.repo === mod.repoPath || c.repo.endsWith(`/${modId}`) || c.repo.includes(modId)));
10266
+ const hasOwnership = codeEntries.some((c) => c.repo && (c.repo === rp || c.repo.endsWith(`/${modId}`) || c.repo.includes(modId)));
10104
10267
  return { id: modId, status: st.status, hasModuleContext: st.moduleContext, hasCurrentState: st.currentState, hasCodebase: st.codebase, hasOwnership };
10105
10268
  });
10106
10269
  for (const ce of codeEntries) {
@@ -10138,12 +10301,14 @@ function getWorkItemContextTool(root, args) {
10138
10301
  warnings.push(`Module "${modId}" not in modules.yml.`);
10139
10302
  return { id: modId, repo: null, moduleContext: null, currentStateSummary: null, codebaseSummary: null };
10140
10303
  }
10141
- const st = detectModuleStatus2(root, mod.repoPath);
10304
+ const rp = moduleRepoPath(mod);
10305
+ const st = detectModuleStatus2(root, rp);
10142
10306
  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 };
10307
+ const mcRelPath = resolveModuleContextPath(root, rp);
10308
+ const mc = args.includeModuleContexts !== false ? readModuleFile(root, rp, mcRelPath) : null;
10309
+ const cs = readModuleFile(root, rp, "knowledge/tech/current-state.md");
10310
+ const cb = readModuleFile(root, rp, "knowledge/tech/codebase.md");
10311
+ return { id: modId, repo: rp, moduleContext: mc, currentStateSummary: cs ? firstParagraph3(cs) : null, codebaseSummary: cb ? firstParagraph3(cb) : null };
10147
10312
  });
10148
10313
  const ownership = { crossRepo: codeEntries.length > 1, items: codeEntries.filter((c) => c.repo).map((c) => ({ repo: c.repo, paths: c.paths ?? [] })) };
10149
10314
  const result = {
@@ -10185,7 +10350,7 @@ function buildBranchStrategy(wi, affectedModules, codeEntries, descriptor) {
10185
10350
  const commitMessages = {};
10186
10351
  for (const modId of affectedModules) {
10187
10352
  const mod = (descriptor.modules ?? []).find((m2) => m2.id === modId);
10188
- const repo = mod?.repoPath ?? modId;
10353
+ const repo = mod ? moduleRepoPath(mod) || modId : modId;
10189
10354
  moduleBranches[repo] = `feature/${wiLower}-${modId}-${slug4.slice(0, 20)}`;
10190
10355
  commitMessages[repo] = `fix(${wiLower}): update ${modId}`;
10191
10356
  }
@@ -10235,7 +10400,9 @@ function exportCapsuleTool(root, args) {
10235
10400
  const descriptor = loadModulesDescriptor(root);
10236
10401
  const mod = (descriptor.modules ?? []).find((m2) => m2.id === args.module);
10237
10402
  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");
10403
+ const rp = moduleRepoPath(mod);
10404
+ const mcRelPath = resolveModuleContextPath(root, rp);
10405
+ const mc = readModuleFile(root, rp, mcRelPath);
10239
10406
  if (!mc) warnings.push("module-context.md not found for this module.");
10240
10407
  if (mc && isPlaceholder(mc)) warnings.push("module-context.md contains placeholder sections.");
10241
10408
  capsuleType = "module";
@@ -10256,7 +10423,7 @@ function exportCapsuleTool(root, args) {
10256
10423
  owners: mod.owner ? [mod.owner] : []
10257
10424
  };
10258
10425
  } else if (role === "module") {
10259
- const mc = readText(root, "knowledge/module/module-context.md");
10426
+ const mc = readText(root, "knowledge/tech/module/module-context.md") ?? readText(root, "knowledge/module/module-context.md");
10260
10427
  if (!mc) warnings.push("module-context.md not found.");
10261
10428
  if (mc && isPlaceholder(mc)) warnings.push("module-context.md contains placeholder sections.");
10262
10429
  const modId = config.module?.id ?? args.module;
@@ -10283,7 +10450,9 @@ function exportCapsuleTool(root, args) {
10283
10450
  const descriptor = loadModulesDescriptor(root);
10284
10451
  const responsibilities = [];
10285
10452
  for (const m2 of descriptor.modules ?? []) {
10286
- const mc = readModuleFile(root, m2.repoPath, "knowledge/module/module-context.md");
10453
+ const rp = moduleRepoPath(m2);
10454
+ const mcRelPath = resolveModuleContextPath(root, rp);
10455
+ const mc = readModuleFile(root, rp, mcRelPath);
10287
10456
  responsibilities.push(mc ? `[${m2.id}] ${firstParagraph3(mc) || m2.name || m2.id}` : `[${m2.id}] ${m2.name || m2.id} (no module-context)`);
10288
10457
  }
10289
10458
  capsuleType = "system";
@@ -10303,7 +10472,7 @@ function exportCapsuleTool(root, args) {
10303
10472
  };
10304
10473
  } else {
10305
10474
  if (role === "module") {
10306
- const mc = readText(root, "knowledge/module/module-context.md");
10475
+ const mc = readText(root, "knowledge/tech/module/module-context.md") ?? readText(root, "knowledge/module/module-context.md");
10307
10476
  if (!mc) warnings.push("module-context.md not found.");
10308
10477
  if (mc && isPlaceholder(mc)) warnings.push("module-context.md contains placeholder sections.");
10309
10478
  const modId = config.module?.id ?? "module";
@@ -10348,6 +10517,70 @@ function exportCapsuleTool(root, args) {
10348
10517
  writeDerived(root, mdPath, mdContent);
10349
10518
  return ok2({ exported: true, type: capsuleType, module: args.module ?? void 0, paths: { json: jsonPath, md: mdPath }, warnings });
10350
10519
  }
10520
+ function modulesDiscoverTool(root, args) {
10521
+ const config = loadConfig2(root);
10522
+ if (!config) return fail2("No Kaddo config found.");
10523
+ if (!isCore2(config)) return fail2("Module discovery is only available from a core repository.");
10524
+ const sysName = config.system?.name ?? config.project?.name ?? "";
10525
+ const workspaceRoots2 = config.multirepo?.workspace_roots ?? [".."];
10526
+ const IGNORED = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", "coverage", "vendor", ".next", "out", "tmp", "temp", ".kaddo"]);
10527
+ const coreAbs = path3.resolve(root);
10528
+ const discovered = [];
10529
+ for (const wr of workspaceRoots2) {
10530
+ const rootAbs = path3.resolve(root, wr);
10531
+ if (!fs3.existsSync(rootAbs) || !fs3.statSync(rootAbs).isDirectory()) continue;
10532
+ let entries;
10533
+ try {
10534
+ entries = fs3.readdirSync(rootAbs);
10535
+ } catch {
10536
+ continue;
10537
+ }
10538
+ for (const entry of entries) {
10539
+ if (IGNORED.has(entry)) continue;
10540
+ const candidateAbs = path3.resolve(rootAbs, entry);
10541
+ if (!fs3.statSync(candidateAbs).isDirectory()) continue;
10542
+ if (candidateAbs === coreAbs) continue;
10543
+ const relPath = path3.relative(root, candidateAbs).replace(/\\/g, "/");
10544
+ const cfgPath = path3.join(candidateAbs, ".kaddo", "config.yml");
10545
+ if (!fs3.existsSync(cfgPath)) {
10546
+ discovered.push({ id: entry, path: relPath, status: "not_configured", eligibleForMapping: false });
10547
+ continue;
10548
+ }
10549
+ let parsed = null;
10550
+ try {
10551
+ parsed = parseYaml10(fs3.readFileSync(cfgPath, "utf-8"));
10552
+ } catch {
10553
+ }
10554
+ const role = parsed?.project?.role ?? parsed?.multirepo?.role;
10555
+ if (role !== "module") {
10556
+ discovered.push({ id: entry, path: relPath, status: "invalid", eligibleForMapping: false, warning: "Not configured as module." });
10557
+ continue;
10558
+ }
10559
+ const modParent = parsed?.multirepo?.parent_system ?? "";
10560
+ if (modParent && modParent !== sysName) {
10561
+ discovered.push({ id: parsed?.module?.id ?? entry, path: relPath, status: "foreign_system", eligibleForMapping: false, parentSystem: modParent });
10562
+ continue;
10563
+ }
10564
+ discovered.push({ id: parsed?.module?.id ?? entry, path: relPath, status: "configured", eligibleForMapping: true });
10565
+ }
10566
+ }
10567
+ if (args.apply) {
10568
+ if (!args.confirm) {
10569
+ return ok2({ discovered, applied: false, message: "Set confirm: true to apply discovered modules." });
10570
+ }
10571
+ const descriptor = loadModulesDescriptor(root);
10572
+ for (const d3 of discovered.filter((dd) => dd.eligibleForMapping)) {
10573
+ if (!(descriptor.modules ?? []).some((m2) => m2.id === d3.id)) {
10574
+ if (!descriptor.modules) descriptor.modules = [];
10575
+ descriptor.modules.push({ id: d3.id, name: d3.id, path: d3.path, parent_system: sysName, status: "configured" });
10576
+ }
10577
+ }
10578
+ const ymlContent = stringifyYaml4({ version: descriptor.version ?? 1, system: sysName, workspace_roots: workspaceRoots2, modules: descriptor.modules ?? [] });
10579
+ writeDerived(root, ".kaddo/modules.yml", ymlContent);
10580
+ return ok2({ discovered, applied: true });
10581
+ }
10582
+ return ok2({ discovered, applied: false });
10583
+ }
10351
10584
  function renderCapsuleMd(data) {
10352
10585
  const fm = ["---", `type: knowledge-capsule`, `system: ${data.system}`, `version: ${data.version}`, `updated_at: ${data.updated_at}`, "---", ""].join("\n");
10353
10586
  const list2 = (items) => items.length === 0 ? "_To be completed._" : items.map((i) => `- ${i}`).join("\n");
@@ -10471,15 +10704,16 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
10471
10704
  if (!knowledgeSummary) {
10472
10705
  missing.push("No project knowledge summary found yet.");
10473
10706
  }
10707
+ const moduleRepo = config != null && isModule(config);
10474
10708
  const roadmapSummary = readMarkdownSummary(dir, "delivery/roadmap.md") ?? "";
10475
- if (!roadmapSummary) {
10709
+ if (!roadmapSummary && !moduleRepo) {
10476
10710
  missing.push("No roadmap baseline found.");
10477
10711
  }
10478
10712
  const allArtifacts = discoverKnowledge(dir);
10479
10713
  const workItems = allArtifacts.filter(
10480
10714
  (a2) => isDeliveryWorkItem(a2) && isActiveState(lifecycleStateOf({ status: a2.status, filePath: a2.filePath }))
10481
10715
  );
10482
- if (workItems.length === 0) {
10716
+ if (workItems.length === 0 && !moduleRepo) {
10483
10717
  missing.push("No work items found.");
10484
10718
  }
10485
10719
  const state = config.project.state;
@@ -10511,8 +10745,8 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
10511
10745
  tech: { status: layerStatusOf("Tech"), artifacts: { "knowledge/tech/codebase.md": qCodebase, "knowledge/tech/current-state.md": qCurrentState } },
10512
10746
  delivery: { status: layerStatusOf("Delivery"), artifacts: { "knowledge/delivery/roadmap.md": qRoadmap } }
10513
10747
  };
10514
- if (qBusiness === "placeholder") missing.push("Business context exists but still looks like a bootstrap placeholder.");
10515
- if (qProduct === "placeholder" || qCapabilities === "placeholder") missing.push("Product capabilities exist but still look like a bootstrap placeholder.");
10748
+ if (qBusiness === "placeholder" && !moduleRepo) missing.push("Business context exists but still looks like a bootstrap placeholder.");
10749
+ if ((qProduct === "placeholder" || qCapabilities === "placeholder") && !moduleRepo) missing.push("Product capabilities exist but still look like a bootstrap placeholder.");
10516
10750
  if (qCurrentState === "placeholder") missing.push("Current state exists but still looks like a bootstrap placeholder.");
10517
10751
  if (qCodebase === "placeholder") missing.push("Codebase map exists but still looks like a bootstrap placeholder.");
10518
10752
  const allWorkItems = allArtifacts.filter((a2) => a2.isWorkItem);
@@ -10535,7 +10769,8 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
10535
10769
  workItemsTotal: allWorkItems.length,
10536
10770
  workItemsWithOwnership: wiWithOwnership,
10537
10771
  workItemsMissingOwnership: allWorkItems.length - wiWithOwnership
10538
- }
10772
+ },
10773
+ isModuleRepo: config != null && isModule(config)
10539
10774
  });
10540
10775
  const nextStepRecommendation = resolveNextStep(dir, now);
10541
10776
  const techDecisions = buildTechDecisions(dir);
@@ -11017,6 +11252,16 @@ function flowForState(state) {
11017
11252
  }
11018
11253
  function buildUnderstandPlan(dir, config) {
11019
11254
  const state = config.project.state;
11255
+ if (isModule(config)) {
11256
+ return {
11257
+ project: { name: config.project.name, state, teamSize: config.team.size, structure: config.project.structure, language: "English" },
11258
+ steps: [],
11259
+ missingAgents: [],
11260
+ agentsInstalled: true,
11261
+ scanAvailable: exists(join(dir, ".kaddo", "scan.json")),
11262
+ contextPackPath: ".kaddo/context-pack.md"
11263
+ };
11264
+ }
11020
11265
  const flow = flowForState(state).filter((s) => !exists(join(dir, s.output)));
11021
11266
  const steps = flow.map((s) => {
11022
11267
  const installed = agentIsInstalled(dir, s.agent);
@@ -11641,6 +11886,18 @@ function createServer(root) {
11641
11886
  },
11642
11887
  async (args) => toolText(guarded(root, () => exportCapsuleTool(root, args)))
11643
11888
  );
11889
+ server.registerTool(
11890
+ "kaddo_modules_discover",
11891
+ {
11892
+ title: "Discover multirepo modules",
11893
+ description: "Discover sibling repositories configured as Kaddo modules. Does not persist without apply+confirm. Core only.",
11894
+ inputSchema: {
11895
+ apply: z3.boolean().optional(),
11896
+ confirm: z3.boolean().optional()
11897
+ }
11898
+ },
11899
+ async (args) => toolText(guarded(root, () => modulesDiscoverTool(root, args)))
11900
+ );
11644
11901
  let installedSkills = [];
11645
11902
  try {
11646
11903
  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.64.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": {