@kaddo/cli 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 +753 -292
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -114,9 +114,15 @@ guard:
114
114
  `;
115
115
  if (meta.role === "core") {
116
116
  yml += `
117
+ system:
118
+ name: ${meta.systemName ?? meta.name}
119
+ `;
120
+ yml += `
117
121
  multirepo:
118
122
  role: core
119
- modules_file: knowledge/modules/modules.md
123
+ modules_file: .kaddo/modules.yml
124
+ workspace_roots:
125
+ - ..
120
126
  `;
121
127
  }
122
128
  if (meta.role === "module") {
@@ -212,21 +218,27 @@ _How the system is deployed: regions, environments, infrastructure._
212
218
  _Auth, observability, error handling, compliance, and other shared concerns._
213
219
  `;
214
220
  }
215
- function buildModulesFile(projectName) {
221
+ function buildModulesFile(systemName3) {
216
222
  return `---
217
223
  type: modules-map
218
- system: ${projectName}
224
+ system: ${systemName3}
219
225
  generated_by: kaddo-init
220
- template_version: 1
226
+ template_version: 2
221
227
  ---
222
228
 
223
- # ${projectName} \u2014 Modules
229
+ # ${systemName3} \u2014 Modules
224
230
 
225
- _List the modules that compose this system. Each module is a repository with its own \`kaddo init\` as a multirepo module._
231
+ _No modules mapped yet. Run \`kaddo modules discover\` to find sibling modules, or \`kaddo modules map <path>\` to register one manually._
232
+ `;
233
+ }
234
+ function buildModulesYml(systemName3) {
235
+ return `version: 1
236
+ system: ${systemName3}
226
237
 
227
- ## Modules
238
+ workspace_roots:
239
+ - ..
228
240
 
229
- _No modules mapped yet. Run \`kaddo modules map\` to register one._
241
+ modules: []
230
242
  `;
231
243
  }
232
244
  function buildKnowledge(projectName) {
@@ -302,25 +314,6 @@ async function runInit() {
302
314
  initialValue: inferredName,
303
315
  validate: (v) => v.trim().length === 0 ? "Project name is required." : void 0
304
316
  });
305
- const state = await select2({
306
- message: "What kind of project is this?",
307
- options: [
308
- { value: "new", label: "New", hint: "Starting from scratch" },
309
- { value: "pre-ai", label: "Pre-AI", hint: "Existing code, not prepared for agents" },
310
- { value: "legacy", label: "Legacy", hint: "Knowledge lives in people's heads" }
311
- ],
312
- initialValue: "pre-ai"
313
- });
314
- const teamSize = await select2({
315
- message: "Team size",
316
- options: [
317
- { value: "indie", label: "Indie", hint: "1 person" },
318
- { value: "small", label: "Small", hint: "2-5 people" },
319
- { value: "medium", label: "Medium", hint: "6-20 people" },
320
- { value: "enterprise", label: "Enterprise", hint: "20+ people" }
321
- ],
322
- initialValue: "small"
323
- });
324
317
  const structure = await select2({
325
318
  message: "Repository structure",
326
319
  options: [
@@ -329,38 +322,65 @@ async function runInit() {
329
322
  ],
330
323
  initialValue: "monorepo"
331
324
  });
332
- const language = await select2({
333
- message: "Project knowledge language (the CLI stays in English)",
334
- options: [
335
- { value: "en", label: "English" },
336
- { value: "es", label: "Spanish" }
337
- ],
338
- initialValue: "en"
339
- });
340
325
  let role;
326
+ let systemName3;
341
327
  let parentSystem;
342
328
  let modId;
343
329
  if (structure === "multirepo") {
344
330
  role = await select2({
345
- message: "Is this repository the core or a module?",
331
+ message: "What is the role of this repository?",
346
332
  options: [
347
333
  { value: "core", label: "Core / system orchestrator" },
348
334
  { value: "module", label: "Module of an existing system" }
349
335
  ]
350
336
  });
337
+ if (role === "core") {
338
+ systemName3 = await text2({
339
+ message: "System name",
340
+ placeholder: "e.g. tryckers",
341
+ validate: (v) => v.trim().length === 0 ? "System name is required." : void 0
342
+ });
343
+ }
351
344
  if (role === "module") {
352
345
  parentSystem = await text2({
353
346
  message: "Parent system name",
354
- placeholder: "e.g. dotear-platform",
347
+ placeholder: "e.g. tryckers",
355
348
  validate: (v) => v.trim().length === 0 ? "Parent system name is required." : void 0
356
349
  });
357
350
  modId = await text2({
358
351
  message: "Module ID",
359
- placeholder: "e.g. loyalty",
352
+ placeholder: "e.g. frontend",
360
353
  validate: (v) => v.trim().length === 0 ? "Module ID is required." : void 0
361
354
  });
362
355
  }
363
356
  }
357
+ const state = await select2({
358
+ message: "What kind of project is this?",
359
+ options: [
360
+ { value: "new", label: "New", hint: "Starting from scratch" },
361
+ { value: "pre-ai", label: "Pre-AI", hint: "Existing code, not prepared for agents" },
362
+ { value: "legacy", label: "Legacy", hint: "Knowledge lives in people's heads" }
363
+ ],
364
+ initialValue: "pre-ai"
365
+ });
366
+ const language = await select2({
367
+ message: "Project knowledge language (the CLI stays in English)",
368
+ options: [
369
+ { value: "en", label: "English" },
370
+ { value: "es", label: "Spanish" }
371
+ ],
372
+ initialValue: "en"
373
+ });
374
+ const teamSize = await select2({
375
+ message: "Team size",
376
+ options: [
377
+ { value: "indie", label: "Indie", hint: "1 person" },
378
+ { value: "small", label: "Small", hint: "2-5 people" },
379
+ { value: "medium", label: "Medium", hint: "6-20 people" },
380
+ { value: "enterprise", label: "Enterprise", hint: "20+ people" }
381
+ ],
382
+ initialValue: "small"
383
+ });
364
384
  const meta = {
365
385
  name: projectName.trim(),
366
386
  state,
@@ -368,6 +388,7 @@ async function runInit() {
368
388
  structure,
369
389
  language,
370
390
  role,
391
+ systemName: systemName3?.trim(),
371
392
  parentSystem: parentSystem?.trim(),
372
393
  moduleId: modId?.trim()
373
394
  };
@@ -375,36 +396,39 @@ async function runInit() {
375
396
  writeFile(kaddoConfigPath, buildConfig(meta));
376
397
  log2.success("Created .kaddo/config.yml");
377
398
  if (role === "module") {
378
- ensureDir(join(dir, ARCH_DIR, "module"));
399
+ ensureDir(join(dir, ARCH_DIR, "tech", "module"));
379
400
  writeFile(
380
- join(dir, ARCH_DIR, "module", "module-context.md"),
401
+ join(dir, ARCH_DIR, "tech", "module", "module-context.md"),
381
402
  buildModuleContext(modId.trim(), parentSystem.trim())
382
403
  );
383
- ensureDir(join(dir, ARCH_DIR, "tech"));
384
404
  if (!exists(join(dir, ARCH_DIR, "tech", "current-state.md"))) {
385
405
  writeFile(join(dir, ARCH_DIR, "tech", "current-state.md"), buildKnowledge(projectName.trim()));
386
406
  }
387
407
  if (!exists(join(dir, ARCH_DIR, "tech", "codebase.md"))) {
388
408
  writeFile(join(dir, ARCH_DIR, "tech", "codebase.md"), buildKnowledge(projectName.trim()));
389
409
  }
390
- ensureDir(join(dir, ARCH_DIR, "delivery", "work-items"));
391
- log2.success("Created knowledge/module/module-context.md");
410
+ log2.success("Created knowledge/tech/module/module-context.md");
392
411
  log2.success("Created knowledge/tech/current-state.md");
393
412
  log2.success("Created knowledge/tech/codebase.md");
394
413
  log2.info("Next: run `kaddo scan` to detect your stack, then use module-context-agent to refine module-context.md.");
395
414
  } else if (role === "core") {
415
+ const sysName = systemName3?.trim() ?? projectName.trim();
396
416
  ensureDir(join(dir, ARCH_DIR, "delivery", "work-items"));
397
- ensureDir(join(dir, ARCH_DIR, "system"));
398
- ensureDir(join(dir, ARCH_DIR, "modules"));
417
+ ensureDir(join(dir, ARCH_DIR, "tech", "system"));
418
+ ensureDir(join(dir, ARCH_DIR, "tech", "modules"));
419
+ ensureDir(join(dir, ARCH_DIR, "agents"));
420
+ ensureDir(join(dir, ARCH_DIR, "skills"));
399
421
  writeFile(join(dir, ARCH_DIR, "knowledge.md"), buildKnowledge(projectName.trim()));
400
422
  writeFile(join(dir, ARCH_DIR, "delivery", "roadmap.md"), buildRoadmap(projectName.trim()));
401
- writeFile(join(dir, ARCH_DIR, "system", "system-context.md"), buildSystemContext(projectName.trim()));
402
- writeFile(join(dir, ARCH_DIR, "modules", "modules.md"), buildModulesFile(projectName.trim()));
423
+ writeFile(join(dir, ARCH_DIR, "tech", "system", "system-context.md"), buildSystemContext(sysName));
424
+ writeFile(join(dir, ARCH_DIR, "tech", "modules", "modules.md"), buildModulesFile(sysName));
425
+ writeFile(join(dir, KADDO_DIR, "modules.yml"), buildModulesYml(sysName));
403
426
  log2.success("Created knowledge/knowledge.md");
404
427
  log2.success("Created knowledge/delivery/roadmap.md");
405
- log2.success("Created knowledge/system/system-context.md");
406
- log2.success("Created knowledge/modules/modules.md");
407
- log2.info("Next: run `kaddo scan` then `kaddo add agents` and `kaddo add skills`.");
428
+ log2.success("Created knowledge/tech/system/system-context.md");
429
+ log2.success("Created knowledge/tech/modules/modules.md");
430
+ log2.success("Created .kaddo/modules.yml");
431
+ log2.info("Next: run `kaddo scan` then `kaddo modules discover` to find sibling modules.");
408
432
  } else {
409
433
  ensureDir(join(dir, ARCH_DIR, "delivery", "work-items"));
410
434
  writeFile(join(dir, ARCH_DIR, "knowledge.md"), buildKnowledge(projectName.trim()));
@@ -1305,10 +1329,14 @@ var configSchema = z.object({
1305
1329
  team: z.object({
1306
1330
  size: teamSizeSchema.default(DEFAULTS.teamSize)
1307
1331
  }).passthrough().default({}),
1332
+ system: z.object({
1333
+ name: z.string().optional()
1334
+ }).passthrough().optional(),
1308
1335
  multirepo: z.object({
1309
1336
  role: multirepoRoleSchema,
1310
1337
  modules_file: z.string().optional(),
1311
- parent_system: z.string().optional()
1338
+ parent_system: z.string().optional(),
1339
+ workspace_roots: z.array(z.string()).optional()
1312
1340
  }).passthrough().optional(),
1313
1341
  module: z.object({
1314
1342
  id: z.string().optional()
@@ -1333,8 +1361,8 @@ function loadConfig(dir) {
1333
1361
  const parsed = configSchema.safeParse(raw ?? {});
1334
1362
  if (!parsed.success) {
1335
1363
  const issues = parsed.error.issues.map((i) => {
1336
- const path7 = i.path.join(".");
1337
- return path7 ? ` - ${path7}: ${i.message}` : ` - ${i.message}`;
1364
+ const path8 = i.path.join(".");
1365
+ return path8 ? ` - ${path8}: ${i.message}` : ` - ${i.message}`;
1338
1366
  }).join("\n");
1339
1367
  throw new ConfigError(`Invalid .kaddo/config.yml:
1340
1368
  ${issues}`);
@@ -1391,6 +1419,15 @@ function projectRole(config) {
1391
1419
  function isModule(config) {
1392
1420
  return projectRole(config) === "module";
1393
1421
  }
1422
+ function isCore(config) {
1423
+ return projectRole(config) === "core";
1424
+ }
1425
+ function systemName(config) {
1426
+ return config.system?.name;
1427
+ }
1428
+ function workspaceRoots(config) {
1429
+ return config.multirepo?.workspace_roots ?? [".."];
1430
+ }
1394
1431
 
1395
1432
  // src/core/command-help.ts
1396
1433
  var COMMAND_HELP = {
@@ -5030,7 +5067,8 @@ var RECOMMENDED_BY_STATE = {
5030
5067
  "backlog-agent.md",
5031
5068
  "work-item-agent.md",
5032
5069
  "implementation-agent.md",
5033
- "adr-agent.md"
5070
+ "adr-agent.md",
5071
+ "module-context-agent.md"
5034
5072
  ],
5035
5073
  "pre-ai": [
5036
5074
  "business-agent.md",
@@ -5040,7 +5078,8 @@ var RECOMMENDED_BY_STATE = {
5040
5078
  "backlog-agent.md",
5041
5079
  "work-item-agent.md",
5042
5080
  "implementation-agent.md",
5043
- "ownership-agent.md"
5081
+ "ownership-agent.md",
5082
+ "module-context-agent.md"
5044
5083
  ],
5045
5084
  legacy: [
5046
5085
  "legacy-agent.md",
@@ -6550,10 +6589,10 @@ function analyzeGuard(touchedFiles, artifacts, silentWithoutOwnership) {
6550
6589
  import { parse as parseYaml4, stringify as stringifyYaml2 } from "yaml";
6551
6590
  var IGNORE_FILE = ".kaddo/ignores.yml";
6552
6591
  function loadIgnores(dir) {
6553
- const path7 = join(dir, IGNORE_FILE);
6554
- if (!exists(path7)) return [];
6592
+ const path8 = join(dir, IGNORE_FILE);
6593
+ if (!exists(path8)) return [];
6555
6594
  try {
6556
- const raw = readFile(path7);
6595
+ const raw = readFile(path8);
6557
6596
  const parsed = parseYaml4(raw);
6558
6597
  return Array.isArray(parsed) ? parsed : [];
6559
6598
  } catch {
@@ -6561,7 +6600,7 @@ function loadIgnores(dir) {
6561
6600
  }
6562
6601
  }
6563
6602
  function saveIgnore(dir, entry) {
6564
- const path7 = join(dir, IGNORE_FILE);
6603
+ const path8 = join(dir, IGNORE_FILE);
6565
6604
  const existing = loadIgnores(dir);
6566
6605
  const idx = existing.findIndex((e) => e.artifact_id === entry.artifact_id);
6567
6606
  if (idx >= 0) {
@@ -6569,17 +6608,17 @@ function saveIgnore(dir, entry) {
6569
6608
  } else {
6570
6609
  existing.push(entry);
6571
6610
  }
6572
- writeFile(path7, stringifyYaml2(existing));
6611
+ writeFile(path8, stringifyYaml2(existing));
6573
6612
  }
6574
6613
  function isIgnored(ignores, artifactId) {
6575
6614
  return ignores.find((e) => e.artifact_id === artifactId);
6576
6615
  }
6577
6616
  function removeIgnore(dir, artifactId) {
6578
- const path7 = join(dir, IGNORE_FILE);
6617
+ const path8 = join(dir, IGNORE_FILE);
6579
6618
  const existing = loadIgnores(dir);
6580
6619
  const filtered = existing.filter((e) => e.artifact_id !== artifactId);
6581
6620
  if (filtered.length === existing.length) return false;
6582
- writeFile(path7, stringifyYaml2(filtered));
6621
+ writeFile(path8, stringifyYaml2(filtered));
6583
6622
  return true;
6584
6623
  }
6585
6624
 
@@ -6601,11 +6640,11 @@ function moduleArtifactCoverage(dir, id) {
6601
6640
  };
6602
6641
  }
6603
6642
  function loadMappedModules(dir) {
6604
- const path7 = join(dir, DESCRIPTOR_PATH);
6605
- if (!exists(path7)) return [];
6643
+ const path8 = join(dir, DESCRIPTOR_PATH);
6644
+ if (!exists(path8)) return [];
6606
6645
  let parsed;
6607
6646
  try {
6608
- parsed = parseYaml5(readFile(path7));
6647
+ parsed = parseYaml5(readFile(path8));
6609
6648
  } catch {
6610
6649
  return [];
6611
6650
  }
@@ -6619,7 +6658,7 @@ function loadMappedModules(dir) {
6619
6658
  modules.push({
6620
6659
  id,
6621
6660
  name: typeof m.name === "string" ? m.name : void 0,
6622
- repoPath: typeof m.repoPath === "string" ? m.repoPath : "",
6661
+ repoPath: typeof m.repoPath === "string" ? m.repoPath : typeof m.path === "string" ? m.path : "",
6623
6662
  type: typeof m.type === "string" ? m.type : void 0,
6624
6663
  mainTechnology: typeof m.mainTechnology === "string" ? m.mainTechnology : void 0,
6625
6664
  owner: typeof m.owner === "string" ? m.owner : void 0,
@@ -6668,13 +6707,15 @@ function detectModuleStatus(coreDir, repoPath) {
6668
6707
  status: "configured",
6669
6708
  configured: true,
6670
6709
  role: "module",
6671
- moduleContext: exists(join(moduleDir2, "knowledge", "module", "module-context.md")),
6710
+ moduleContext: exists(join(moduleDir2, "knowledge", "tech", "module", "module-context.md")) || exists(join(moduleDir2, "knowledge", "module", "module-context.md")),
6672
6711
  currentState: exists(join(moduleDir2, "knowledge", "tech", "current-state.md")),
6673
6712
  codebase: exists(join(moduleDir2, "knowledge", "tech", "codebase.md"))
6674
6713
  };
6675
6714
  }
6676
6715
  function readModuleContext(coreDir, repoPath) {
6677
- const p2 = join(coreDir, repoPath, "knowledge", "module", "module-context.md");
6716
+ const newPath = join(coreDir, repoPath, "knowledge", "tech", "module", "module-context.md");
6717
+ const legacyPath = join(coreDir, repoPath, "knowledge", "module", "module-context.md");
6718
+ const p2 = exists(newPath) ? newPath : legacyPath;
6678
6719
  if (!exists(p2)) return null;
6679
6720
  try {
6680
6721
  return readFile(p2);
@@ -7085,7 +7126,7 @@ function buildGuardHistory(dir) {
7085
7126
  const d = hotspotDir(t.code_path);
7086
7127
  hotspotMap.set(d, (hotspotMap.get(d) ?? 0) + 1);
7087
7128
  }
7088
- const hotspots = [...hotspotMap.entries()].map(([path7, warnings]) => ({ path: path7, warnings })).sort((a, b) => b.warnings - a.warnings);
7129
+ const hotspots = [...hotspotMap.entries()].map(([path8, warnings]) => ({ path: path8, warnings })).sort((a, b) => b.warnings - a.warnings);
7089
7130
  return {
7090
7131
  available: true,
7091
7132
  total_runs: sorted.length,
@@ -7487,7 +7528,7 @@ function runIgnoreRemove(artifactId) {
7487
7528
 
7488
7529
  // src/commands/explain.ts
7489
7530
  import matter6 from "gray-matter";
7490
- import { parse as parseYaml10 } from "yaml";
7531
+ import { parse as parseYaml12 } from "yaml";
7491
7532
 
7492
7533
  // src/core/delivery-phase.ts
7493
7534
  function layer(layers, name) {
@@ -7548,6 +7589,34 @@ function firstUnrefinedLayerAgent(layers) {
7548
7589
  return { agent: "architecture-agent", step: "Use architecture-agent to complete knowledge/tech/current-state.md" };
7549
7590
  }
7550
7591
  function assessPhase(input) {
7592
+ if (input.isModuleRepo) {
7593
+ const techStatus = layer(input.layers, "Tech");
7594
+ const techReady = !NOT_READY_LAYER.includes(techStatus);
7595
+ if (techReady) {
7596
+ return {
7597
+ phase: "Active Delivery",
7598
+ reasons: ["Module repo \u2014 Tech layer ready", "Business/Product managed by core"],
7599
+ recommendedAgents: [],
7600
+ nextStep: "Module knowledge is ready for core orchestration.",
7601
+ llmInstructions: [
7602
+ "This is a module repo. Business, Product, and Delivery are managed by the core repo.",
7603
+ "Do not create business, product, roadmap or Work Items here."
7604
+ ]
7605
+ };
7606
+ }
7607
+ return {
7608
+ phase: "Knowledge Refinement",
7609
+ reasons: ["Module repo \u2014 Tech layer incomplete", "Business/Product managed by core"],
7610
+ recommendedAgents: ["module-context-agent"],
7611
+ nextStep: "Use module-context-agent to refine knowledge/tech/module/module-context.md",
7612
+ llmInstructions: [
7613
+ "Use the module-context-agent to refine the module context.",
7614
+ "Do not create business, product, roadmap or Work Items in this module.",
7615
+ "Do not install agents or skills.",
7616
+ "Do not write application code."
7617
+ ]
7618
+ };
7619
+ }
7551
7620
  const phase = determinePhase(input);
7552
7621
  const reasons = buildReasons(input);
7553
7622
  const bs = input.workItems.byState;
@@ -7653,10 +7722,10 @@ function assessPhase(input) {
7653
7722
 
7654
7723
  // src/core/capsule.ts
7655
7724
  import matter3 from "gray-matter";
7656
- import { parse as parseYaml9, stringify as stringifyYaml4 } from "yaml";
7725
+ import { parse as parseYaml10, stringify as stringifyYaml5 } from "yaml";
7657
7726
 
7658
7727
  // src/commands/modules-map.ts
7659
- import { parse as parseYaml8, stringify as stringifyYaml3 } from "yaml";
7728
+ import { parse as parseYaml9, stringify as stringifyYaml4 } from "yaml";
7660
7729
 
7661
7730
  // src/templates/registry.ts
7662
7731
  var QUALITY = "## Quality checklist";
@@ -9143,9 +9212,373 @@ function getTemplate(id) {
9143
9212
  return KADDO_TEMPLATES.find((t) => t.id === id);
9144
9213
  }
9145
9214
 
9215
+ // src/commands/modules-discover.ts
9216
+ import path5 from "path";
9217
+ import { parse as parseYaml8, stringify as stringifyYaml3 } from "yaml";
9218
+ var CONFIG_PATH3 = ".kaddo/config.yml";
9219
+ var MODULES_YML = ".kaddo/modules.yml";
9220
+ var MODULES_MD = "knowledge/tech/modules/modules.md";
9221
+ var IGNORED_DIRS = /* @__PURE__ */ new Set([
9222
+ ".git",
9223
+ "node_modules",
9224
+ "dist",
9225
+ "build",
9226
+ "coverage",
9227
+ "vendor",
9228
+ ".next",
9229
+ "out",
9230
+ "tmp",
9231
+ "temp",
9232
+ ".kaddo"
9233
+ ]);
9234
+ function readModulesYml(dir) {
9235
+ const p2 = join(dir, MODULES_YML);
9236
+ if (!exists(p2)) return { version: 1, system: "", workspace_roots: [".."], modules: [] };
9237
+ try {
9238
+ const parsed = parseYaml8(readFile(p2));
9239
+ return {
9240
+ version: parsed.version ?? 1,
9241
+ system: parsed.system ?? "",
9242
+ workspace_roots: parsed.workspace_roots ?? [".."],
9243
+ modules: Array.isArray(parsed.modules) ? parsed.modules : []
9244
+ };
9245
+ } catch {
9246
+ return { version: 1, system: "", workspace_roots: [".."], modules: [] };
9247
+ }
9248
+ }
9249
+ function writeModulesYml(dir, yml) {
9250
+ writeFile(join(dir, MODULES_YML), stringifyYaml3(yml));
9251
+ }
9252
+ function readRepoConfig(repoDir) {
9253
+ const configPath = join(repoDir, CONFIG_PATH3);
9254
+ if (!exists(configPath)) return null;
9255
+ try {
9256
+ return parseYaml8(readFile(configPath));
9257
+ } catch {
9258
+ return null;
9259
+ }
9260
+ }
9261
+ function hasKnowledgeFile(repoDir, ...segments) {
9262
+ return exists(join(repoDir, "knowledge", ...segments));
9263
+ }
9264
+ function hasModuleContext(repoDir) {
9265
+ return hasKnowledgeFile(repoDir, "tech", "module", "module-context.md") || hasKnowledgeFile(repoDir, "module", "module-context.md");
9266
+ }
9267
+ function hasCurrentState(repoDir) {
9268
+ return hasKnowledgeFile(repoDir, "tech", "current-state.md");
9269
+ }
9270
+ function hasCodebase(repoDir) {
9271
+ return hasKnowledgeFile(repoDir, "tech", "codebase.md");
9272
+ }
9273
+ function buildModuleContextPaths(repoDir) {
9274
+ const ctx = {};
9275
+ if (hasKnowledgeFile(repoDir, "tech", "module", "module-context.md")) {
9276
+ ctx.module = "knowledge/tech/module/module-context.md";
9277
+ } else if (hasKnowledgeFile(repoDir, "module", "module-context.md")) {
9278
+ ctx.module = "knowledge/module/module-context.md";
9279
+ }
9280
+ if (hasCurrentState(repoDir)) ctx.current_state = "knowledge/tech/current-state.md";
9281
+ if (hasCodebase(repoDir)) ctx.codebase = "knowledge/tech/codebase.md";
9282
+ return ctx;
9283
+ }
9284
+ function discoverModules(dir) {
9285
+ const config = loadConfig(dir);
9286
+ if (!config || !isCore(config)) return [];
9287
+ const sysName = systemName(config) ?? config.project.name;
9288
+ const roots = workspaceRoots(config);
9289
+ const existing = readModulesYml(dir);
9290
+ const seenIds = /* @__PURE__ */ new Set();
9291
+ const seenPaths = /* @__PURE__ */ new Set();
9292
+ const results = [];
9293
+ const coreAbsPath = path5.resolve(dir);
9294
+ for (const root of roots) {
9295
+ const rootAbs = path5.resolve(dir, root);
9296
+ if (!exists(rootAbs) || !isDir(rootAbs)) continue;
9297
+ let entries;
9298
+ try {
9299
+ entries = readDir(rootAbs);
9300
+ } catch {
9301
+ continue;
9302
+ }
9303
+ for (const entry of entries) {
9304
+ if (IGNORED_DIRS.has(entry)) continue;
9305
+ const candidateAbs = path5.resolve(rootAbs, entry);
9306
+ if (!isDir(candidateAbs)) continue;
9307
+ if (candidateAbs === coreAbsPath) continue;
9308
+ const relPath = path5.relative(dir, candidateAbs).replace(/\\/g, "/");
9309
+ const repoConfig = readRepoConfig(candidateAbs);
9310
+ if (!repoConfig) {
9311
+ results.push({
9312
+ id: entry,
9313
+ name: entry,
9314
+ path: relPath.startsWith(".") ? relPath : `./${relPath}`,
9315
+ parentSystem: "",
9316
+ status: "not_configured",
9317
+ eligibleForMapping: false,
9318
+ moduleContext: false,
9319
+ currentState: false,
9320
+ codebase: false
9321
+ });
9322
+ continue;
9323
+ }
9324
+ const project = repoConfig.project;
9325
+ const multirepo = repoConfig.multirepo;
9326
+ const module = repoConfig.module;
9327
+ const role = project?.role ?? multirepo?.role;
9328
+ if (role !== "module") {
9329
+ results.push({
9330
+ id: entry,
9331
+ name: project?.name ?? entry,
9332
+ path: relPath.startsWith(".") ? relPath : `./${relPath}`,
9333
+ parentSystem: "",
9334
+ status: "invalid",
9335
+ eligibleForMapping: false,
9336
+ warning: "Repository has Kaddo but is not configured as a module.",
9337
+ moduleContext: false,
9338
+ currentState: false,
9339
+ codebase: false
9340
+ });
9341
+ continue;
9342
+ }
9343
+ const modId = module?.id ?? entry;
9344
+ const modParent = multirepo?.parent_system ?? "";
9345
+ const modName = project?.name ?? entry;
9346
+ const modPath = relPath.startsWith(".") ? relPath : `./${relPath}`;
9347
+ if (modParent && modParent !== sysName) {
9348
+ results.push({
9349
+ id: modId,
9350
+ name: modName,
9351
+ path: modPath,
9352
+ parentSystem: modParent,
9353
+ status: "foreign_system",
9354
+ eligibleForMapping: false,
9355
+ warning: `parent system: ${modParent}`,
9356
+ moduleContext: hasModuleContext(candidateAbs),
9357
+ currentState: hasCurrentState(candidateAbs),
9358
+ codebase: hasCodebase(candidateAbs)
9359
+ });
9360
+ continue;
9361
+ }
9362
+ let status = "configured";
9363
+ let warning;
9364
+ let eligible = true;
9365
+ if (seenIds.has(modId)) {
9366
+ status = "duplicate";
9367
+ warning = `duplicate module id: ${modId}`;
9368
+ eligible = false;
9369
+ } else if (seenPaths.has(modPath)) {
9370
+ status = "duplicate";
9371
+ warning = `duplicate path: ${modPath}`;
9372
+ eligible = false;
9373
+ }
9374
+ seenIds.add(modId);
9375
+ seenPaths.add(modPath);
9376
+ results.push({
9377
+ id: modId,
9378
+ name: modName,
9379
+ path: modPath,
9380
+ parentSystem: modParent || sysName,
9381
+ status,
9382
+ eligibleForMapping: eligible,
9383
+ warning,
9384
+ moduleContext: hasModuleContext(candidateAbs),
9385
+ currentState: hasCurrentState(candidateAbs),
9386
+ codebase: hasCodebase(candidateAbs)
9387
+ });
9388
+ }
9389
+ }
9390
+ for (const m of existing.modules) {
9391
+ const absPath = path5.resolve(dir, m.path);
9392
+ if (!exists(absPath) && !results.some((r) => r.path === m.path)) {
9393
+ results.push({
9394
+ id: m.id,
9395
+ name: m.name,
9396
+ path: m.path,
9397
+ parentSystem: m.parent_system,
9398
+ status: "missing",
9399
+ eligibleForMapping: false,
9400
+ warning: "Previously registered but path no longer exists.",
9401
+ moduleContext: false,
9402
+ currentState: false,
9403
+ codebase: false
9404
+ });
9405
+ }
9406
+ }
9407
+ return results;
9408
+ }
9409
+ function applyDiscovery(dir, discovered) {
9410
+ const config = loadConfig(dir);
9411
+ const sysName = systemName(config) ?? config.project.name;
9412
+ const yml = readModulesYml(dir);
9413
+ yml.system = sysName;
9414
+ const eligible = discovered.filter((d) => d.eligibleForMapping && d.status === "configured");
9415
+ for (const d of eligible) {
9416
+ const existingIdx = yml.modules.findIndex((m) => m.id === d.id);
9417
+ const entry = {
9418
+ id: d.id,
9419
+ name: d.name,
9420
+ path: d.path,
9421
+ parent_system: d.parentSystem,
9422
+ status: d.status,
9423
+ context: buildModuleContextPaths(path5.resolve(dir, d.path))
9424
+ };
9425
+ if (existingIdx >= 0) {
9426
+ yml.modules[existingIdx] = entry;
9427
+ } else {
9428
+ yml.modules.push(entry);
9429
+ }
9430
+ }
9431
+ writeModulesYml(dir, yml);
9432
+ updateModulesMd(dir, yml);
9433
+ return { mapped: eligible.length };
9434
+ }
9435
+ function updateModulesMd(dir, yml) {
9436
+ const lines = [
9437
+ "---",
9438
+ "type: modules-map",
9439
+ `system: ${yml.system}`,
9440
+ "generated_by: kaddo-modules-discover",
9441
+ "template_version: 2",
9442
+ "---",
9443
+ "",
9444
+ `# ${yml.system} \u2014 Modules`,
9445
+ ""
9446
+ ];
9447
+ if (yml.modules.length === 0) {
9448
+ lines.push("_No modules mapped yet. Run `kaddo modules discover` to find sibling modules._");
9449
+ } else {
9450
+ for (const m of yml.modules) {
9451
+ lines.push(`## ${m.id}`);
9452
+ lines.push("");
9453
+ lines.push(`- Repository: \`${m.path}\``);
9454
+ lines.push(`- Status: ${m.status}`);
9455
+ lines.push(`- Parent system: ${m.parent_system}`);
9456
+ const ctxPaths = m.context ?? {};
9457
+ lines.push(`- Module context: ${ctxPaths.module ? "available" : "missing"}`);
9458
+ lines.push("");
9459
+ }
9460
+ }
9461
+ writeFile(join(dir, MODULES_MD), lines.join("\n"));
9462
+ }
9463
+ function runModulesValidate(dir = cwd()) {
9464
+ const yml = readModulesYml(dir);
9465
+ if (yml.modules.length === 0) {
9466
+ console.log("No modules registered in .kaddo/modules.yml.");
9467
+ return;
9468
+ }
9469
+ const config = loadConfig(dir);
9470
+ const sysName = systemName(config) ?? config.project.name;
9471
+ const legacy = detectLegacyPaths(dir);
9472
+ if (legacy.length > 0) {
9473
+ console.log("");
9474
+ console.log("Legacy multirepo knowledge paths detected.");
9475
+ console.log("");
9476
+ console.log("Recommended migration:");
9477
+ for (const l of legacy) {
9478
+ console.log(`- ${l.from}`);
9479
+ console.log(` \u2192 ${l.to}`);
9480
+ console.log("");
9481
+ }
9482
+ }
9483
+ console.log("");
9484
+ for (const m of yml.modules) {
9485
+ const absPath = path5.resolve(dir, m.path);
9486
+ if (!exists(absPath)) {
9487
+ console.log(`${m.id.padEnd(16)} missing`);
9488
+ continue;
9489
+ }
9490
+ const repoConfig = readRepoConfig(absPath);
9491
+ if (!repoConfig) {
9492
+ console.log(`${m.id.padEnd(16)} not_configured`);
9493
+ continue;
9494
+ }
9495
+ const project = repoConfig.project;
9496
+ const multirepo = repoConfig.multirepo;
9497
+ const role = project?.role ?? multirepo?.role;
9498
+ if (role !== "module") {
9499
+ console.log(`${m.id.padEnd(16)} invalid`);
9500
+ continue;
9501
+ }
9502
+ const modParent = multirepo?.parent_system;
9503
+ if (modParent && modParent !== sysName) {
9504
+ console.log(`${m.id.padEnd(16)} foreign_system`);
9505
+ continue;
9506
+ }
9507
+ console.log(`${m.id.padEnd(16)} valid`);
9508
+ }
9509
+ console.log("");
9510
+ }
9511
+ function detectLegacyPaths(dir) {
9512
+ const migrations = [];
9513
+ if (exists(join(dir, "knowledge", "system", "system-context.md"))) {
9514
+ migrations.push({ from: "knowledge/system/system-context.md", to: "knowledge/tech/system/system-context.md" });
9515
+ }
9516
+ if (exists(join(dir, "knowledge", "modules", "modules.md"))) {
9517
+ migrations.push({ from: "knowledge/modules/modules.md", to: "knowledge/tech/modules/modules.md" });
9518
+ }
9519
+ if (exists(join(dir, "knowledge", "module", "module-context.md"))) {
9520
+ migrations.push({ from: "knowledge/module/module-context.md", to: "knowledge/tech/module/module-context.md" });
9521
+ }
9522
+ return migrations;
9523
+ }
9524
+ async function runModulesDiscover(opts = {}) {
9525
+ const dir = cwd();
9526
+ intro2("kaddo modules discover");
9527
+ const config = loadConfig(dir);
9528
+ if (!config) {
9529
+ console.error("Kaddo is not initialized. Run `kaddo init` first.");
9530
+ process.exit(1);
9531
+ }
9532
+ if (!isCore(config)) {
9533
+ console.error("Module discovery is only available from a core repository.");
9534
+ process.exit(1);
9535
+ }
9536
+ const discovered = discoverModules(dir);
9537
+ if (discovered.length === 0) {
9538
+ log2.info("No sibling repositories found in workspace roots.");
9539
+ outro2("Discovery complete.");
9540
+ return;
9541
+ }
9542
+ console.log("");
9543
+ console.log("Workspace module discovery");
9544
+ console.log("");
9545
+ for (const d of discovered) {
9546
+ const icon = d.status === "configured" ? "\u2713" : d.status === "not_configured" ? "\u25CB" : d.status === "foreign_system" ? "!" : d.status === "duplicate" ? "!" : d.status === "missing" ? "\u2717" : "\u2717";
9547
+ console.log(`${icon} ${d.id}`);
9548
+ console.log(` ${d.path}`);
9549
+ console.log(` status: ${d.status}`);
9550
+ if (d.warning) console.log(` ${d.warning}`);
9551
+ console.log("");
9552
+ }
9553
+ const eligible = discovered.filter((d) => d.eligibleForMapping && d.status === "configured");
9554
+ console.log(`Eligible for mapping: ${eligible.length}`);
9555
+ if (eligible.length === 0) {
9556
+ outro2("Discovery complete.");
9557
+ return;
9558
+ }
9559
+ if (opts.apply) {
9560
+ const confirmed = await confirm2({
9561
+ message: `Map ${eligible.length} configured Kaddo modules?`,
9562
+ initialValue: true
9563
+ });
9564
+ if (!confirmed) {
9565
+ outro2("No changes applied.");
9566
+ return;
9567
+ }
9568
+ const result = applyDiscovery(dir, discovered);
9569
+ log2.success(`Mapped ${result.mapped} configured modules.`);
9570
+ log2.success("Updated .kaddo/modules.yml.");
9571
+ log2.success("Updated knowledge/tech/modules/modules.md.");
9572
+ outro2("Discovery applied.");
9573
+ } else {
9574
+ log2.info("Run `kaddo modules discover --apply` to persist eligible modules.");
9575
+ outro2("Discovery complete.");
9576
+ }
9577
+ }
9578
+
9146
9579
  // src/commands/modules-map.ts
9147
9580
  var DESCRIPTOR_PATH2 = ".kaddo/modules.yml";
9148
- var CONFIG_PATH3 = ".kaddo/config.yml";
9581
+ var CONFIG_PATH4 = ".kaddo/config.yml";
9149
9582
  var MODULE_TYPES = [
9150
9583
  "frontend",
9151
9584
  "backend",
@@ -9160,17 +9593,17 @@ function slugify2(name) {
9160
9593
  return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
9161
9594
  }
9162
9595
  function readModulesDescriptor(dir) {
9163
- const path7 = join(dir, DESCRIPTOR_PATH2);
9164
- if (!exists(path7)) return { version: 1, modules: [] };
9596
+ const path8 = join(dir, DESCRIPTOR_PATH2);
9597
+ if (!exists(path8)) return { version: 1, modules: [] };
9165
9598
  try {
9166
- const parsed = parseYaml8(readFile(path7));
9599
+ const parsed = parseYaml9(readFile(path8));
9167
9600
  return { version: parsed.version ?? 1, modules: parsed.modules ?? [] };
9168
9601
  } catch {
9169
9602
  return { version: 1, modules: [] };
9170
9603
  }
9171
9604
  }
9172
9605
  function writeModulesDescriptor(dir, descriptor) {
9173
- writeFile(join(dir, DESCRIPTOR_PATH2), stringifyYaml3(descriptor));
9606
+ writeFile(join(dir, DESCRIPTOR_PATH2), stringifyYaml4(descriptor));
9174
9607
  }
9175
9608
  function moduleDir(id) {
9176
9609
  return `knowledge/tech/modules/${id}`;
@@ -9301,70 +9734,74 @@ function mapModule(dir, input) {
9301
9734
  }
9302
9735
  return { module: mod, written, skipped, alreadyRegistered };
9303
9736
  }
9304
- async function runModulesMap(dir = cwd()) {
9737
+ async function runModulesMap(dir = cwd(), modulePath) {
9305
9738
  intro2("kaddo modules map");
9306
- if (!exists(join(dir, CONFIG_PATH3))) {
9739
+ if (!exists(join(dir, CONFIG_PATH4))) {
9307
9740
  console.error("Kaddo is not initialized in this project.");
9308
9741
  console.error("Run `kaddo init` first.");
9309
9742
  process.exit(1);
9310
9743
  }
9311
- log2.info("Register a secondary repository as a module of this system.");
9744
+ let repoPath;
9745
+ if (modulePath) {
9746
+ repoPath = modulePath;
9747
+ } else {
9748
+ repoPath = await text2({
9749
+ message: "Repository path (relative to this repo)",
9750
+ placeholder: "e.g. ../frontend",
9751
+ validate: (v) => v.trim().length === 0 ? "Repository path is required." : void 0
9752
+ });
9753
+ }
9754
+ const targetDir = join(dir, repoPath.trim());
9755
+ if (!exists(targetDir)) {
9756
+ log2.warn(`Path "${repoPath.trim()}" does not exist.`);
9757
+ } else {
9758
+ const st = detectModuleStatus(dir, repoPath.trim());
9759
+ if (st.status === "not_configured") {
9760
+ log2.warn(`${repoPath.trim()} does not have Kaddo configured. Run \`kaddo init\` inside that repository first.`);
9761
+ } else if (st.status === "invalid") {
9762
+ log2.warn(`${repoPath.trim()} has Kaddo but is not configured as a module.`);
9763
+ }
9764
+ }
9312
9765
  const name = await text2({
9313
9766
  message: "Module name",
9314
9767
  placeholder: "e.g. Frontend Web",
9315
9768
  validate: (v) => v.trim().length === 0 ? "Name is required." : void 0
9316
9769
  });
9317
- const repoPath = await text2({
9318
- message: "Repository path (relative to this repo)",
9319
- placeholder: "e.g. ../frontend",
9320
- validate: (v) => v.trim().length === 0 ? "Repository path is required." : void 0
9321
- });
9322
9770
  const type = await select2({
9323
9771
  message: "Module type",
9324
9772
  options: MODULE_TYPES.map((t) => ({ value: t, label: t }))
9325
9773
  });
9326
- const mainTechnology = await text2({
9327
- message: "Main technology (optional)",
9328
- placeholder: "e.g. Next.js"
9329
- });
9330
- const owner = await text2({
9331
- message: "Owner (optional)",
9332
- placeholder: "e.g. web-team"
9333
- });
9334
- const capabilitiesRaw = await text2({
9335
- message: "Related capabilities (comma-separated, optional)",
9336
- placeholder: "e.g. customer-dashboard, loyalty-portal"
9337
- });
9338
- if (!exists(join(dir, repoPath.trim()))) {
9339
- log2.warn(`Path "${repoPath.trim()}" does not exist yet \u2014 registering anyway.`);
9340
- }
9341
9774
  const result = mapModule(dir, {
9342
9775
  name,
9343
9776
  repoPath,
9344
- type,
9345
- mainTechnology,
9346
- owner,
9347
- capabilities: capabilitiesRaw.split(",").map((c) => c.trim()).filter(Boolean)
9777
+ type
9348
9778
  });
9349
9779
  for (const p2 of result.written) log2.success(`Created ${p2}`);
9350
9780
  for (const p2 of result.skipped) log2.info(`Kept existing ${p2}`);
9351
9781
  log2.success(`Module "${result.module.id}" recorded in ${DESCRIPTOR_PATH2}`);
9352
- outro2(
9353
- "Module mapped. Use the `module-design-agent` in your LLM with `.kaddo/context-pack.md` to fill in module-design.md."
9354
- );
9782
+ outro2("Module mapped.");
9355
9783
  }
9356
9784
  function runModulesList(dir = cwd()) {
9357
- const descriptor = readModulesDescriptor(dir);
9358
- if (descriptor.modules.length === 0) {
9359
- console.log("No modules mapped. Run `kaddo modules map` to register one.");
9785
+ const yml = readModulesYml(dir);
9786
+ if (yml.modules.length === 0) {
9787
+ console.log("");
9788
+ console.log("No modules are mapped for this core repository.");
9789
+ console.log("");
9790
+ console.log("Run:");
9791
+ console.log(" kaddo modules discover");
9792
+ console.log("");
9793
+ console.log("Or map one manually:");
9794
+ console.log(" kaddo modules map <path>");
9795
+ console.log("");
9360
9796
  return;
9361
9797
  }
9362
9798
  console.log("");
9363
9799
  console.log("Mapped modules:");
9364
- for (const m of descriptor.modules) {
9365
- const st = detectModuleStatus(dir, m.repoPath);
9800
+ for (const m of yml.modules) {
9801
+ const mPath = m.path ?? m.repoPath ?? "";
9802
+ const st = detectModuleStatus(dir, mPath);
9366
9803
  const statusLabel = st.status === "configured" ? "\u2713 configured" : st.status === "not_configured" ? "\u25CB not configured" : "\u2717 invalid";
9367
- console.log(` ${m.id.padEnd(16)} ${m.type.padEnd(14)} ${statusLabel.padEnd(18)} ${m.repoPath}`);
9804
+ console.log(` ${m.id.padEnd(16)} ${statusLabel.padEnd(18)} ${mPath}`);
9368
9805
  if (st.warning) console.log(` \u26A0 ${st.warning}`);
9369
9806
  }
9370
9807
  console.log("");
@@ -9505,14 +9942,14 @@ function loadExternalRegistry(dir) {
9505
9942
  const p2 = join(dir, EXTERNAL_PATH);
9506
9943
  if (!exists(p2)) return [];
9507
9944
  try {
9508
- const parsed = parseYaml9(readFile(p2));
9945
+ const parsed = parseYaml10(readFile(p2));
9509
9946
  return Array.isArray(parsed?.external) ? parsed.external : [];
9510
9947
  } catch {
9511
9948
  return [];
9512
9949
  }
9513
9950
  }
9514
9951
  function serializeExternalRegistry(entries) {
9515
- return stringifyYaml4({ external: entries });
9952
+ return stringifyYaml5({ external: entries });
9516
9953
  }
9517
9954
  function sectionList(md, title) {
9518
9955
  const re = new RegExp(`^##\\s+${title}\\s*$`, "im");
@@ -9539,7 +9976,7 @@ function sectionParagraph(md, title) {
9539
9976
  }
9540
9977
  return "";
9541
9978
  }
9542
- function parseCapsule(id, path7, md) {
9979
+ function parseCapsule(id, path8, md) {
9543
9980
  const { data } = matter3(md);
9544
9981
  const updatedAt = data.updated_at ? String(data.updated_at) : void 0;
9545
9982
  let ageDays = null;
@@ -9549,7 +9986,7 @@ function parseCapsule(id, path7, md) {
9549
9986
  }
9550
9987
  return {
9551
9988
  id,
9552
- path: path7,
9989
+ path: path8,
9553
9990
  system: data.system ? String(data.system) : id,
9554
9991
  owner: data.owner ? String(data.owner) : void 0,
9555
9992
  updatedAt,
@@ -9635,9 +10072,9 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
9635
10072
  ];
9636
10073
  const presentLayers = [];
9637
10074
  for (const layer2 of layerDocs) {
9638
- const path7 = layer2.files.map((f) => `${KNOWLEDGE3}/${f}`).find((rel) => exists(join(dir, rel)));
9639
- if (path7) {
9640
- addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path: path7 });
10075
+ const path8 = layer2.files.map((f) => `${KNOWLEDGE3}/${f}`).find((rel) => exists(join(dir, rel)));
10076
+ if (path8) {
10077
+ addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path: path8 });
9641
10078
  presentLayers.push(layer2.id);
9642
10079
  }
9643
10080
  }
@@ -10280,6 +10717,21 @@ function layerQuality(dir, layer2) {
10280
10717
  return worst;
10281
10718
  }
10282
10719
  function knowledgeLayers(dir) {
10720
+ const config = loadConfig(dir);
10721
+ if (config && isModule(config)) {
10722
+ return [
10723
+ { layer: "Business", status: "Not applicable", detected: [] },
10724
+ { layer: "Product", status: "Not applicable", detected: [] },
10725
+ ...discoverLayers(dir).filter((l) => l.layer === "Tech").map((l) => {
10726
+ if (l.status !== "Consolidated" && l.status !== "Structured") return l;
10727
+ const q = layerQuality(dir, l.layer);
10728
+ if (q === "placeholder") return { ...l, status: "Placeholder" };
10729
+ if (q === "weak") return { ...l, status: "Weak" };
10730
+ return l;
10731
+ }),
10732
+ { layer: "Delivery", status: "Managed by core", detected: [] }
10733
+ ];
10734
+ }
10283
10735
  return discoverLayers(dir).map((l) => {
10284
10736
  if (l.status !== "Consolidated" && l.status !== "Structured") return l;
10285
10737
  const q = layerQuality(dir, l.layer);
@@ -10965,6 +11417,9 @@ function buildSharedFileStatuses(statuses) {
10965
11417
  }));
10966
11418
  }
10967
11419
 
11420
+ // src/core/next-step.ts
11421
+ import { parse as parseYaml11 } from "yaml";
11422
+
10968
11423
  // src/core/decisions.ts
10969
11424
  var CANDIDATES_DISCOVERY = "knowledge/tech/discovery/decision-candidates.md";
10970
11425
  var CANDIDATES_LEGACY = "knowledge/tech/decision-candidates.md";
@@ -11080,6 +11535,14 @@ function buildDeliveryState(dir) {
11080
11535
  adapters_installed: adapters.length
11081
11536
  };
11082
11537
  }
11538
+ function hasRegisteredModules(modulesYmlPath) {
11539
+ try {
11540
+ const parsed = parseYaml11(readFile(modulesYmlPath));
11541
+ return Array.isArray(parsed?.modules) && parsed.modules.length > 0;
11542
+ } catch {
11543
+ return false;
11544
+ }
11545
+ }
11083
11546
  function safeRead(p2) {
11084
11547
  try {
11085
11548
  return readFile(p2);
@@ -11129,12 +11592,20 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
11129
11592
  }
11130
11593
  const state = config.project.state;
11131
11594
  const q = (rel) => analyzeKnowledgeArtifact(dir, rel);
11595
+ const resolveAgent = (agent) => {
11596
+ const file = agent.endsWith(".md") ? agent : `${agent}.md`;
11597
+ const path8 = agentInstallPath(file);
11598
+ const installed = isFile(join(dir, path8));
11599
+ const group = agentGroupOf(file);
11600
+ return { agentPath: path8, agentInstalled: installed, installCommand: installed ? void 0 : `kaddo add agents --group ${group}` };
11601
+ };
11132
11602
  const moduleRepo = isModule(config);
11133
- const MC = "knowledge/module/module-context.md";
11603
+ const coreRepo = isCore(config);
11604
+ const MC = exists(join(dir, "knowledge/tech/module/module-context.md")) ? "knowledge/tech/module/module-context.md" : "knowledge/module/module-context.md";
11134
11605
  if (moduleRepo) {
11135
11606
  const qMC = q(MC);
11136
11607
  if (qMC === "missing") {
11137
- 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." };
11608
+ 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." };
11138
11609
  }
11139
11610
  if ((state === "pre-ai" || state === "legacy") && !exists(join(dir, ".kaddo", "scan.json"))) {
11140
11611
  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." };
@@ -11143,15 +11614,26 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
11143
11614
  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." };
11144
11615
  }
11145
11616
  if (qMC !== "useful") {
11617
+ const ar = resolveAgent("module-context-agent");
11146
11618
  return {
11147
11619
  id: "refine-module-context",
11148
11620
  phase: "Knowledge Refinement",
11149
- label: "Use module-context-agent to refine `knowledge/module/module-context.md`.",
11621
+ label: "Use module-context-agent to refine `knowledge/tech/module/module-context.md`.",
11150
11622
  agent: "module-context-agent",
11151
11623
  skill: "module-context-refinement",
11152
11624
  target: MC,
11625
+ agentPath: ar.agentPath,
11626
+ agentInstalled: ar.agentInstalled,
11627
+ installCommand: ar.installCommand,
11153
11628
  reason: "Module context is still a placeholder.",
11154
- instructions: ["Refine the module context with real knowledge about this module.", "Do not create business.md or product.md.", "Do not write code."]
11629
+ instructions: [
11630
+ "Refine the module context using evidence from this repository.",
11631
+ "Use the parent system context when available.",
11632
+ "Do not create business, product, roadmap or Work Items in this module.",
11633
+ "Do not install agents or skills in this module.",
11634
+ "Do not write application code.",
11635
+ "Preserve frontmatter and record unknowns as open questions."
11636
+ ]
11155
11637
  };
11156
11638
  }
11157
11639
  const qCurrentState2 = q(CS), qCodebase2 = q(CB);
@@ -11180,12 +11662,18 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
11180
11662
  if (!exists(join(dir, ".kaddo", "understand.md"))) {
11181
11663
  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." };
11182
11664
  }
11183
- 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." };
11665
+ 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." };
11184
11666
  }
11185
11667
  const qBusiness = q(B), qProduct = q(P), qCap = q(CAP), qCurrentState = q(CS), qCodebase = q(CB);
11186
- if (qBusiness === "missing" || qProduct === "missing") {
11668
+ if (!coreRepo && (qBusiness === "missing" || qProduct === "missing")) {
11187
11669
  return { id: "bootstrap", phase: "Setup", label: "Run `kaddo bootstrap` to create the project knowledge baseline.", command: "kaddo bootstrap", reason: "The knowledge baseline is incomplete." };
11188
11670
  }
11671
+ if (coreRepo) {
11672
+ const modulesYmlPath = join(dir, ".kaddo", "modules.yml");
11673
+ if (!exists(modulesYmlPath) || !hasRegisteredModules(modulesYmlPath)) {
11674
+ 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." };
11675
+ }
11676
+ }
11189
11677
  const ctx = buildCodexAdapterContext(dir);
11190
11678
  if (!ctx.hasAgents) {
11191
11679
  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." };
@@ -11200,13 +11688,6 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
11200
11688
  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." };
11201
11689
  }
11202
11690
  const discovery = state === "pre-ai" || state === "legacy";
11203
- const resolveAgent = (agent) => {
11204
- const file = agent.endsWith(".md") ? agent : `${agent}.md`;
11205
- const path7 = agentInstallPath(file);
11206
- const installed = isFile(join(dir, path7));
11207
- const group = agentGroupOf(file);
11208
- return { agentPath: path7, agentInstalled: installed, installCommand: installed ? void 0 : `kaddo add agents --group ${group}` };
11209
- };
11210
11691
  const refine = (id, agent, target, quality, verb = "complete") => {
11211
11692
  const ar = resolveAgent(agent);
11212
11693
  return {
@@ -11945,6 +12426,67 @@ var identifyRisks = {
11945
12426
  return { status: hasLegacy ? "done" : "pending", agent: hasLegacy ? void 0 : "legacy-agent" };
11946
12427
  }
11947
12428
  };
12429
+ var refineModuleContext = {
12430
+ id: "refine-module-context",
12431
+ label: "Refine module context",
12432
+ evaluate: (ctx) => {
12433
+ const newPath = join(ctx.dir, "knowledge/tech/module/module-context.md");
12434
+ const legacyPath = join(ctx.dir, "knowledge/module/module-context.md");
12435
+ const mcPath = exists(newPath) ? "knowledge/tech/module/module-context.md" : exists(legacyPath) ? "knowledge/module/module-context.md" : null;
12436
+ if (!mcPath) return { status: "pending", agent: "module-context-agent", skill: "module-context-refinement" };
12437
+ const q = analyzeKnowledgeArtifact(ctx.dir, mcPath);
12438
+ if (q === "useful") return { status: "done", evidence: [mcPath] };
12439
+ return { status: "current", agent: "module-context-agent", skill: "module-context-refinement", evidence: [mcPath], reason: "Module context is still a placeholder." };
12440
+ }
12441
+ };
12442
+ var describeModuleCurrentState = {
12443
+ id: "describe-current-state",
12444
+ label: "Describe current state",
12445
+ evaluate: (ctx) => ({
12446
+ status: isUseful(ctx.qCurrentState) ? "done" : "pending",
12447
+ evidence: isUseful(ctx.qCurrentState) ? ["knowledge/tech/current-state.md"] : void 0,
12448
+ agent: isUseful(ctx.qCurrentState) ? void 0 : "architecture-agent"
12449
+ })
12450
+ };
12451
+ var mapModuleCodebase = {
12452
+ id: "map-codebase",
12453
+ label: "Map codebase",
12454
+ evaluate: (ctx) => ({
12455
+ status: isUseful(ctx.qCodebase) ? "done" : "pending",
12456
+ evidence: isUseful(ctx.qCodebase) ? ["knowledge/tech/codebase.md"] : void 0,
12457
+ agent: isUseful(ctx.qCodebase) ? void 0 : "architecture-agent"
12458
+ })
12459
+ };
12460
+ var validateModuleKnowledge = {
12461
+ id: "validate-module-knowledge",
12462
+ label: "Validate module knowledge",
12463
+ evaluate: (ctx) => {
12464
+ const hasUnderstand = exists(join(ctx.dir, ".kaddo/understand.md"));
12465
+ if (hasUnderstand) return { status: "done", evidence: [".kaddo/understand.md"] };
12466
+ return { status: "pending", command: "kaddo understand" };
12467
+ }
12468
+ };
12469
+ var readyForCoreOrchestration = {
12470
+ id: "ready-for-core-orchestration",
12471
+ label: "Ready for core orchestration",
12472
+ evaluate: (ctx) => {
12473
+ const newPath = join(ctx.dir, "knowledge/tech/module/module-context.md");
12474
+ const legacyPath = join(ctx.dir, "knowledge/module/module-context.md");
12475
+ const mcPath = exists(newPath) ? "knowledge/tech/module/module-context.md" : exists(legacyPath) ? "knowledge/module/module-context.md" : null;
12476
+ const mcUseful = mcPath ? analyzeKnowledgeArtifact(ctx.dir, mcPath) === "useful" : false;
12477
+ const allDone = mcUseful && isUseful(ctx.qCurrentState) && isUseful(ctx.qCodebase);
12478
+ return { status: allDone ? "done" : "pending", reason: allDone ? void 0 : "Module knowledge is not yet complete." };
12479
+ }
12480
+ };
12481
+ var MODULE_STEPS = [
12482
+ enableKaddo,
12483
+ scanRepository,
12484
+ refineModuleContext,
12485
+ describeModuleCurrentState,
12486
+ mapModuleCodebase,
12487
+ validateModuleKnowledge,
12488
+ readyForCoreOrchestration
12489
+ ];
11948
12490
  var NEW_STEPS = [
11949
12491
  enableKaddo,
11950
12492
  defineBusiness,
@@ -12085,17 +12627,18 @@ function mapNextStepId(id) {
12085
12627
  "review-work-item": "refine-work-item",
12086
12628
  "prepare-implementation": "prepare-implementation",
12087
12629
  "implement-work-item": "prepare-implementation",
12088
- "refine-module-context": "refine-work-item",
12630
+ "refine-module-context": "refine-module-context",
12089
12631
  "init-module-context": "enable-kaddo",
12090
- "module-ready": "prepare-implementation",
12091
- "validate-work-item-modules": "prepare-implementation"
12632
+ "module-ready": "ready-for-core-orchestration",
12633
+ "validate-work-item-modules": "validate-module-knowledge",
12634
+ "modules-discover": "enable-kaddo"
12092
12635
  };
12093
12636
  return MAP[id] ?? id;
12094
12637
  }
12095
12638
  function buildProjectRoute(dir) {
12096
12639
  const config = loadConfig(dir);
12097
12640
  const state = config?.project.state ?? "pre-ai";
12098
- const defs = stepsForState(state);
12641
+ const defs = config && isModule(config) ? MODULE_STEPS : stepsForState(state);
12099
12642
  const ctx = buildRouteContext(dir);
12100
12643
  const steps = defs.map((d) => {
12101
12644
  const result = d.evaluate(ctx);
@@ -12434,7 +12977,8 @@ function buildProjectExplanation(dir) {
12434
12977
  roadmapQuality: buildRoadmapQuality(dir),
12435
12978
  projectRoute: buildProjectRoute(dir),
12436
12979
  scanSignals: loadScanSignals(dir),
12437
- metadataHealth: analyzeMetadataHealth(dir)
12980
+ metadataHealth: analyzeMetadataHealth(dir),
12981
+ isModuleRepo: config != null && isModule(config)
12438
12982
  };
12439
12983
  }
12440
12984
  function stateLabel(state) {
@@ -12759,7 +13303,7 @@ function renderExplanationAgent(exp) {
12759
13303
 
12760
13304
  // src/commands/explain.ts
12761
13305
  var ARCH_DIR5 = "knowledge";
12762
- var CONFIG_PATH4 = ".kaddo/config.yml";
13306
+ var CONFIG_PATH5 = ".kaddo/config.yml";
12763
13307
  function readKnowledge(dir) {
12764
13308
  const knowledgePath = join(dir, ARCH_DIR5, "knowledge.md");
12765
13309
  if (!exists(knowledgePath)) return null;
@@ -12783,10 +13327,10 @@ function readRoadmap(dir) {
12783
13327
  }
12784
13328
  }
12785
13329
  function readConfig(dir) {
12786
- const configPath = join(dir, CONFIG_PATH4);
13330
+ const configPath = join(dir, CONFIG_PATH5);
12787
13331
  if (!exists(configPath)) return {};
12788
13332
  try {
12789
- return parseYaml10(readFile(configPath));
13333
+ return parseYaml12(readFile(configPath));
12790
13334
  } catch {
12791
13335
  return {};
12792
13336
  }
@@ -12897,7 +13441,7 @@ function explainForAgent(dir, artifacts, opts) {
12897
13441
  console.log(JSON.stringify(output, null, 2));
12898
13442
  }
12899
13443
  function explainProject(dir, mode) {
12900
- if (!exists(join(dir, CONFIG_PATH4))) {
13444
+ if (!exists(join(dir, CONFIG_PATH5))) {
12901
13445
  console.error("No .kaddo/config.yml found. Run `kaddo init` first.");
12902
13446
  process.exit(1);
12903
13447
  }
@@ -13028,15 +13572,16 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
13028
13572
  if (!knowledgeSummary) {
13029
13573
  missing.push("No project knowledge summary found yet.");
13030
13574
  }
13575
+ const moduleRepo = config != null && isModule(config);
13031
13576
  const roadmapSummary = readMarkdownSummary(dir, "delivery/roadmap.md") ?? "";
13032
- if (!roadmapSummary) {
13577
+ if (!roadmapSummary && !moduleRepo) {
13033
13578
  missing.push("No roadmap baseline found.");
13034
13579
  }
13035
13580
  const allArtifacts = discoverKnowledge(dir);
13036
13581
  const workItems = allArtifacts.filter(
13037
13582
  (a) => isDeliveryWorkItem(a) && isActiveState(lifecycleStateOf({ status: a.status, filePath: a.filePath }))
13038
13583
  );
13039
- if (workItems.length === 0) {
13584
+ if (workItems.length === 0 && !moduleRepo) {
13040
13585
  missing.push("No work items found.");
13041
13586
  }
13042
13587
  const state = config.project.state;
@@ -13068,8 +13613,8 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
13068
13613
  tech: { status: layerStatusOf("Tech"), artifacts: { "knowledge/tech/codebase.md": qCodebase, "knowledge/tech/current-state.md": qCurrentState } },
13069
13614
  delivery: { status: layerStatusOf("Delivery"), artifacts: { "knowledge/delivery/roadmap.md": qRoadmap } }
13070
13615
  };
13071
- if (qBusiness === "placeholder") missing.push("Business context exists but still looks like a bootstrap placeholder.");
13072
- if (qProduct === "placeholder" || qCapabilities === "placeholder") missing.push("Product capabilities exist but still look like a bootstrap placeholder.");
13616
+ if (qBusiness === "placeholder" && !moduleRepo) missing.push("Business context exists but still looks like a bootstrap placeholder.");
13617
+ if ((qProduct === "placeholder" || qCapabilities === "placeholder") && !moduleRepo) missing.push("Product capabilities exist but still look like a bootstrap placeholder.");
13073
13618
  if (qCurrentState === "placeholder") missing.push("Current state exists but still looks like a bootstrap placeholder.");
13074
13619
  if (qCodebase === "placeholder") missing.push("Codebase map exists but still looks like a bootstrap placeholder.");
13075
13620
  const allWorkItems = allArtifacts.filter((a) => a.isWorkItem);
@@ -13092,7 +13637,8 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
13092
13637
  workItemsTotal: allWorkItems.length,
13093
13638
  workItemsWithOwnership: wiWithOwnership,
13094
13639
  workItemsMissingOwnership: allWorkItems.length - wiWithOwnership
13095
- }
13640
+ },
13641
+ isModuleRepo: config != null && isModule(config)
13096
13642
  });
13097
13643
  const nextStepRecommendation = resolveNextStep(dir, now);
13098
13644
  const techDecisions = buildTechDecisions(dir);
@@ -13613,6 +14159,16 @@ function flowForState(state) {
13613
14159
  }
13614
14160
  function buildUnderstandPlan(dir, config) {
13615
14161
  const state = config.project.state;
14162
+ if (isModule(config)) {
14163
+ return {
14164
+ project: { name: config.project.name, state, teamSize: config.team.size, structure: config.project.structure, language: "English" },
14165
+ steps: [],
14166
+ missingAgents: [],
14167
+ agentsInstalled: true,
14168
+ scanAvailable: exists(join(dir, ".kaddo", "scan.json")),
14169
+ contextPackPath: ".kaddo/context-pack.md"
14170
+ };
14171
+ }
13616
14172
  const flow = flowForState(state).filter((s) => !exists(join(dir, s.output)));
13617
14173
  const steps = flow.map((s) => {
13618
14174
  const installed = agentIsInstalled(dir, s.agent);
@@ -13969,7 +14525,7 @@ function renderUnderstandTerminal(plan) {
13969
14525
  }
13970
14526
 
13971
14527
  // src/core/delivery.ts
13972
- import { parse as parseYaml11 } from "yaml";
14528
+ import { parse as parseYaml13 } from "yaml";
13973
14529
  function slugify4(s) {
13974
14530
  return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
13975
14531
  }
@@ -14203,7 +14759,7 @@ function runUnderstand() {
14203
14759
  }
14204
14760
 
14205
14761
  // src/core/classifier.ts
14206
- import path5 from "path";
14762
+ import path6 from "path";
14207
14763
  var MIGRATION_PATTERNS = [
14208
14764
  /supabase\/migrations\//,
14209
14765
  /prisma\/migrations\//,
@@ -14242,7 +14798,7 @@ var DEP_PATTERNS = [
14242
14798
  /^pom\.xml$/
14243
14799
  ];
14244
14800
  function normalizePath2(p2) {
14245
- return p2.split(path5.sep).join("/");
14801
+ return p2.split(path6.sep).join("/");
14246
14802
  }
14247
14803
  function matchesAny2(file, patterns) {
14248
14804
  const normalized = normalizePath2(file);
@@ -14420,14 +14976,14 @@ async function runClassify(opts = {}) {
14420
14976
  }
14421
14977
 
14422
14978
  // src/commands/status.ts
14423
- import { parse as parseYaml12 } from "yaml";
14979
+ import { parse as parseYaml14 } from "yaml";
14424
14980
  var ARCH_DIR8 = "knowledge";
14425
- var CONFIG_PATH5 = ".kaddo/config.yml";
14981
+ var CONFIG_PATH6 = ".kaddo/config.yml";
14426
14982
  function loadConfig3(dir) {
14427
- const p2 = join(dir, CONFIG_PATH5);
14983
+ const p2 = join(dir, CONFIG_PATH6);
14428
14984
  if (!exists(p2)) return {};
14429
14985
  try {
14430
- return parseYaml12(readFile(p2));
14986
+ return parseYaml14(readFile(p2));
14431
14987
  } catch {
14432
14988
  return {};
14433
14989
  }
@@ -14629,13 +15185,13 @@ function runHistory(opts = {}) {
14629
15185
  }
14630
15186
 
14631
15187
  // src/commands/add.ts
14632
- import { parse as parseYaml13, stringify as stringifyYaml5 } from "yaml";
14633
- var CONFIG_PATH6 = ".kaddo/config.yml";
15188
+ import { parse as parseYaml15, stringify as stringifyYaml6 } from "yaml";
15189
+ var CONFIG_PATH7 = ".kaddo/config.yml";
14634
15190
  function readProjectState(dir) {
14635
- const configPath = join(dir, CONFIG_PATH6);
15191
+ const configPath = join(dir, CONFIG_PATH7);
14636
15192
  if (!exists(configPath)) return void 0;
14637
15193
  try {
14638
- const config = parseYaml13(readFile(configPath));
15194
+ const config = parseYaml15(readFile(configPath));
14639
15195
  return config.project?.state;
14640
15196
  } catch {
14641
15197
  return void 0;
@@ -14656,23 +15212,23 @@ function selectSkillModuleFiles(files, opts) {
14656
15212
  return { files: selected, label };
14657
15213
  }
14658
15214
  function markModuleInstalled(dir, configKey, moduleName) {
14659
- const configPath = join(dir, CONFIG_PATH6);
15215
+ const configPath = join(dir, CONFIG_PATH7);
14660
15216
  if (!exists(configPath)) return;
14661
15217
  try {
14662
- const config = parseYaml13(readFile(configPath));
15218
+ const config = parseYaml15(readFile(configPath));
14663
15219
  config[configKey] = { installed: true, installed_at: (/* @__PURE__ */ new Date()).toISOString().split("T")[0] };
14664
15220
  const modules = config.modules ?? [];
14665
15221
  if (!modules.includes(moduleName)) modules.push(moduleName);
14666
15222
  config.modules = modules;
14667
- writeFile(configPath, stringifyYaml5(config));
15223
+ writeFile(configPath, stringifyYaml6(config));
14668
15224
  } catch {
14669
15225
  }
14670
15226
  }
14671
15227
  function isModuleInstalled(dir, configKey) {
14672
- const configPath = join(dir, CONFIG_PATH6);
15228
+ const configPath = join(dir, CONFIG_PATH7);
14673
15229
  if (!exists(configPath)) return false;
14674
15230
  try {
14675
- const config = parseYaml13(readFile(configPath));
15231
+ const config = parseYaml15(readFile(configPath));
14676
15232
  const moduleConfig = config[configKey];
14677
15233
  return moduleConfig?.installed === true;
14678
15234
  } catch {
@@ -14696,11 +15252,16 @@ function runAdd(moduleName, opts = {}, dir = cwd()) {
14696
15252
  process.exit(1);
14697
15253
  }
14698
15254
  intro2(`kaddo add ${mod.name}`);
14699
- if (!exists(join(dir, CONFIG_PATH6))) {
15255
+ if (!exists(join(dir, CONFIG_PATH7))) {
14700
15256
  console.error("Kaddo is not initialized in this project.");
14701
15257
  console.error("Run `kaddo init` first.");
14702
15258
  process.exit(1);
14703
15259
  }
15260
+ const addConfig = loadConfig(dir);
15261
+ if ((mod.name === "agents" || mod.name === "skills") && addConfig && isModule(addConfig)) {
15262
+ console.error(`Module repos do not use ${mod.name}. ${mod.name === "agents" ? "Agents" : "Skills"} are managed by the core repo.`);
15263
+ process.exit(1);
15264
+ }
14704
15265
  const alreadyInstalled = isModuleInstalled(dir, mod.configKey);
14705
15266
  for (const d of mod.dirs) {
14706
15267
  ensureDir(join(dir, d));
@@ -14901,11 +15462,11 @@ function applyOwnership(raw, globs, mode = "replace") {
14901
15462
  }
14902
15463
 
14903
15464
  // src/commands/owners.ts
14904
- var CONFIG_PATH7 = ".kaddo/config.yml";
15465
+ var CONFIG_PATH8 = ".kaddo/config.yml";
14905
15466
  var CUSTOM_GLOB = "__custom__";
14906
15467
  function runOwners(opts) {
14907
15468
  const dir = cwd();
14908
- if (!exists(join(dir, CONFIG_PATH7))) {
15469
+ if (!exists(join(dir, CONFIG_PATH8))) {
14909
15470
  console.error("No .kaddo/config.yml found. Run `kaddo init` first.");
14910
15471
  process.exit(1);
14911
15472
  }
@@ -14995,7 +15556,7 @@ async function chooseGlobs(suggested) {
14995
15556
  }
14996
15557
  async function runOwnersSuggest() {
14997
15558
  const dir = cwd();
14998
- if (!exists(join(dir, CONFIG_PATH7))) {
15559
+ if (!exists(join(dir, CONFIG_PATH8))) {
14999
15560
  console.error("No .kaddo/config.yml found. Run `kaddo init` first.");
15000
15561
  process.exit(1);
15001
15562
  }
@@ -15070,113 +15631,6 @@ ${globs.map((g) => ` - ${g}`).join("\n")}`);
15070
15631
  outro2("Ownership declared.");
15071
15632
  }
15072
15633
 
15073
- // src/commands/module-descriptor.ts
15074
- import { parse as parseYaml14, stringify as stringifyYaml6 } from "yaml";
15075
- var DESCRIPTOR_PATH3 = "knowledge/module.yml";
15076
- function readDescriptor(dir) {
15077
- const path7 = join(dir, DESCRIPTOR_PATH3);
15078
- if (!exists(path7)) return null;
15079
- try {
15080
- return parseYaml14(readFile(path7));
15081
- } catch {
15082
- return null;
15083
- }
15084
- }
15085
- function writeDescriptor(dir, descriptor) {
15086
- writeFile(join(dir, DESCRIPTOR_PATH3), stringifyYaml6(descriptor));
15087
- }
15088
- async function runModuleDescriptor(opts) {
15089
- const dir = cwd();
15090
- if (opts.show || !opts.init && exists(join(dir, DESCRIPTOR_PATH3))) {
15091
- const descriptor2 = readDescriptor(dir);
15092
- if (!descriptor2) {
15093
- console.log("No module descriptor found. Run `kaddo module --init` to create one.");
15094
- return;
15095
- }
15096
- printDescriptor(descriptor2);
15097
- return;
15098
- }
15099
- intro2("kaddo module --init");
15100
- log2.info("Creates knowledge/module.yml \u2014 declares this repository's identity for multirepo contexts.");
15101
- const name = await text2({
15102
- message: "Module name (this repository)",
15103
- placeholder: "e.g. payments-service",
15104
- validate: (v) => v.trim().length === 0 ? "Name is required." : void 0
15105
- });
15106
- const purpose = await text2({
15107
- message: "Purpose \u2014 what does this module do?",
15108
- placeholder: "e.g. Handles subscription billing, invoicing, and payment processing",
15109
- validate: (v) => v.trim().length === 0 ? "Purpose is required." : void 0
15110
- });
15111
- const stack = await text2({
15112
- message: "Stack (comma-separated)",
15113
- placeholder: "e.g. TypeScript, Node.js, PostgreSQL, Stripe"
15114
- });
15115
- const responsibilities = await text2({
15116
- message: "Key responsibilities (comma-separated)",
15117
- placeholder: "e.g. Charge customers, Issue refunds, Manage subscriptions"
15118
- });
15119
- const contracts = await text2({
15120
- message: "Contracts exposed (APIs, events, comma-separated)",
15121
- placeholder: "e.g. POST /charge, event:payment.succeeded, event:payment.failed"
15122
- });
15123
- const dependencies = await text2({
15124
- message: "Dependencies on other modules (comma-separated)",
15125
- placeholder: "e.g. auth-service, user-service"
15126
- });
15127
- const boundaries = await text2({
15128
- message: "What does this module NOT own?",
15129
- placeholder: "e.g. User identity, Email delivery, Inventory"
15130
- });
15131
- const ownership = await text2({
15132
- message: "Team or owner",
15133
- placeholder: "e.g. platform-team"
15134
- });
15135
- const splitTrimmed = (s) => s.split(",").map((x) => x.trim()).filter(Boolean);
15136
- const descriptor = {
15137
- name: name.trim(),
15138
- purpose: purpose.trim(),
15139
- stack: splitTrimmed(stack),
15140
- responsibilities: splitTrimmed(responsibilities),
15141
- contracts: splitTrimmed(contracts),
15142
- dependencies: splitTrimmed(dependencies),
15143
- boundaries: splitTrimmed(boundaries),
15144
- ownership: ownership.trim(),
15145
- related_artifacts: []
15146
- };
15147
- writeDescriptor(dir, descriptor);
15148
- log2.success(`Created ${DESCRIPTOR_PATH3}`);
15149
- outro2(`Module descriptor ready. Share knowledge/module.yml with dependent repositories.`);
15150
- }
15151
- function printDescriptor(d) {
15152
- console.log("");
15153
- console.log(`Module: ${d.name}`);
15154
- console.log(`Purpose: ${d.purpose}`);
15155
- if (d.stack?.length) console.log(`Stack: ${d.stack.join(", ")}`);
15156
- if (d.ownership) console.log(`Owner: ${d.ownership}`);
15157
- if (d.responsibilities?.length) {
15158
- console.log("Responsibilities:");
15159
- d.responsibilities.forEach((r) => console.log(` - ${r}`));
15160
- }
15161
- if (d.contracts?.length) {
15162
- console.log("Contracts:");
15163
- d.contracts.forEach((c) => console.log(` - ${c}`));
15164
- }
15165
- if (d.dependencies?.length) {
15166
- console.log("Dependencies:");
15167
- d.dependencies.forEach((dep) => console.log(` - ${dep}`));
15168
- }
15169
- if (d.boundaries?.length) {
15170
- console.log("Boundaries (not owned):");
15171
- d.boundaries.forEach((b) => console.log(` - ${b}`));
15172
- }
15173
- if (d.related_artifacts?.length) {
15174
- console.log("Related artifacts:");
15175
- d.related_artifacts.forEach((a) => console.log(` - ${a}`));
15176
- }
15177
- console.log("");
15178
- }
15179
-
15180
15634
  // src/core/bootstrap-templates.ts
15181
15635
  function fm(type, state) {
15182
15636
  return `---
@@ -15608,7 +16062,7 @@ function baselineTemplate(kind, state) {
15608
16062
  }
15609
16063
 
15610
16064
  // src/commands/bootstrap.ts
15611
- var CONFIG_PATH8 = ".kaddo/config.yml";
16065
+ var CONFIG_PATH9 = ".kaddo/config.yml";
15612
16066
  var FILE_TARGETS = [
15613
16067
  { path: "knowledge/business/business.md", kind: "business" },
15614
16068
  { path: "knowledge/product/product.md", kind: "product" },
@@ -15665,7 +16119,7 @@ function bootstrap(dir) {
15665
16119
  }
15666
16120
  async function runBootstrap(dir = cwd()) {
15667
16121
  intro2("kaddo bootstrap");
15668
- if (!exists(join(dir, CONFIG_PATH8))) {
16122
+ if (!exists(join(dir, CONFIG_PATH9))) {
15669
16123
  console.error("Kaddo is not initialized in this project.");
15670
16124
  console.error("Run `kaddo init` first.");
15671
16125
  process.exit(1);
@@ -16257,7 +16711,7 @@ function runReportImpact(opts = {}) {
16257
16711
  }
16258
16712
 
16259
16713
  // src/core/savings.ts
16260
- import { parse as parseYaml15 } from "yaml";
16714
+ import { parse as parseYaml16 } from "yaml";
16261
16715
  var DEFAULT_ASSUMPTIONS = {
16262
16716
  currency: "USD",
16263
16717
  hourly_cost: 40,
@@ -16275,7 +16729,7 @@ function loadAssumptions(dir) {
16275
16729
  const p2 = join(dir, SAVINGS_PATH);
16276
16730
  if (!exists(p2)) return { assumptions: { ...DEFAULT_ASSUMPTIONS }, source: "default" };
16277
16731
  try {
16278
- const raw = parseYaml15(readFile(p2));
16732
+ const raw = parseYaml16(readFile(p2));
16279
16733
  const a = raw?.assumptions ?? {};
16280
16734
  const t = raw?.team ?? {};
16281
16735
  const num = (v, d) => typeof v === "number" && Number.isFinite(v) ? v : d;
@@ -17057,7 +17511,7 @@ function runAssetsUpdate(kind, opts = {}) {
17057
17511
 
17058
17512
  // src/commands/ready.ts
17059
17513
  import fs4 from "fs";
17060
- import path6 from "path";
17514
+ import path7 from "path";
17061
17515
  import matter11 from "gray-matter";
17062
17516
  function validateReadiness(content, frontmatter) {
17063
17517
  const warnings = [];
@@ -17120,10 +17574,10 @@ function transitionToReady(filePath) {
17120
17574
  const posix = filePath.replace(/\\/g, "/");
17121
17575
  let newPath = filePath;
17122
17576
  if (posix.includes("/work-items/draft/")) {
17123
- const readyDir = path6.dirname(filePath).replace(/[/\\]draft$/, path6.sep + "ready");
17577
+ const readyDir = path7.dirname(filePath).replace(/[/\\]draft$/, path7.sep + "ready");
17124
17578
  ensureDir(readyDir);
17125
- const fileName = path6.basename(filePath);
17126
- newPath = path6.join(readyDir, fileName);
17579
+ const fileName = path7.basename(filePath);
17580
+ newPath = path7.join(readyDir, fileName);
17127
17581
  }
17128
17582
  return { newPath, newContent };
17129
17583
  }
@@ -17194,8 +17648,8 @@ async function runReady(id, opts = {}) {
17194
17648
  fs4.unlinkSync(wi.filePath);
17195
17649
  log2.success(`Updated status: ready`);
17196
17650
  log2.success(`Moved file:`);
17197
- log2.info(` ${path6.relative(dir, wi.filePath)}`);
17198
- log2.info(` \u2192 ${path6.relative(dir, newPath)}`);
17651
+ log2.info(` ${path7.relative(dir, wi.filePath)}`);
17652
+ log2.info(` \u2192 ${path7.relative(dir, newPath)}`);
17199
17653
  } else {
17200
17654
  writeFile(wi.filePath, newContent);
17201
17655
  log2.success(`Updated status: ready`);
@@ -17229,8 +17683,8 @@ var capsuleCmd = program.command("capsule").description("Export this project as
17229
17683
  capsuleCmd.command("export").description("Write a Knowledge Capsule about this project to .kaddo/exports/").option("--scope <scope>", 'Export scope: "system" includes all mapped modules').option("--module <id>", "Export a capsule for a specific mapped module").action((opts) => {
17230
17684
  runCapsuleExport(opts);
17231
17685
  });
17232
- capsuleCmd.command("add <path>").description("Register an external Knowledge Capsule as project context (.kaddo/external.yml)").action((path7) => {
17233
- runCapsuleAdd(path7);
17686
+ capsuleCmd.command("add <path>").description("Register an external Knowledge Capsule as project context (.kaddo/external.yml)").action((path8) => {
17687
+ runCapsuleAdd(path8);
17234
17688
  });
17235
17689
  var graphCmd = program.command("graph").description("Export the lightweight, file-based knowledge graph of the project");
17236
17690
  graphCmd.command("export").description("Write the knowledge graph to .kaddo/graph.json and .kaddo/graph.mmd").option("--scope <scope>", "Graph scope: active (default) or all").option("--format <format>", "Output format: json, mermaid (default: both)").action((opts) => {
@@ -17328,16 +17782,23 @@ program.command("owners [action]").description("List domain owners, or run `owne
17328
17782
  runOwners(opts);
17329
17783
  }
17330
17784
  });
17331
- program.command("module").description("Show or initialize the multirepo module descriptor (knowledge/module.yml)").option("--init", "Create the module descriptor interactively").option("--show", "Print the current module descriptor").action(async (opts) => {
17332
- await runModuleDescriptor(opts);
17785
+ program.command("module").description("(deprecated) Use `kaddo modules` instead").allowUnknownOption().action(() => {
17786
+ console.log("Unknown command `module`.");
17787
+ console.log("Did you mean `kaddo modules list`?");
17333
17788
  });
17334
- var modulesCmd = program.command("modules").description("Map and list secondary repositories as modules of the system (multirepo)");
17335
- modulesCmd.command("map").description("Register a secondary repository as a module and generate its knowledge structure").action(async () => {
17336
- await runModulesMap();
17789
+ var modulesCmd = program.command("modules").description("Discover, map, list and validate multirepo modules");
17790
+ modulesCmd.command("discover").description("Discover sibling repositories configured as Kaddo modules").option("--apply", "Persist discovered modules to .kaddo/modules.yml").action(async (opts) => {
17791
+ await runModulesDiscover(opts);
17337
17792
  });
17338
- modulesCmd.command("list").description("List mapped modules").action(() => {
17793
+ modulesCmd.command("map [path]").description("Register a repository as a module manually").action(async (modulePath) => {
17794
+ await runModulesMap(void 0, modulePath);
17795
+ });
17796
+ modulesCmd.command("list").description("List mapped modules from .kaddo/modules.yml").action(() => {
17339
17797
  runModulesList();
17340
17798
  });
17799
+ modulesCmd.command("validate").description("Validate registered modules against their current configuration").action(() => {
17800
+ runModulesValidate();
17801
+ });
17341
17802
  program.command("add [module]").description("Install an optional Kaddo module (adr, incident, rfc, migration, legacy, agents, skills, standards, security, stack, git-strategy)").option("--all", "For `add agents` / `add skills`: install every item (not just the recommended set)").option("--group <name>", "For `add agents`: business|product|tech|delivery|utilities. For `add skills`: delivery|tech|integration").action((moduleName, opts) => {
17342
17803
  runAdd(moduleName ?? "", { all: opts.all, group: opts.group });
17343
17804
  });