@kaddo/cli 3.62.0 → 3.63.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +600 -274
  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 = {
@@ -6550,10 +6587,10 @@ function analyzeGuard(touchedFiles, artifacts, silentWithoutOwnership) {
6550
6587
  import { parse as parseYaml4, stringify as stringifyYaml2 } from "yaml";
6551
6588
  var IGNORE_FILE = ".kaddo/ignores.yml";
6552
6589
  function loadIgnores(dir) {
6553
- const path7 = join(dir, IGNORE_FILE);
6554
- if (!exists(path7)) return [];
6590
+ const path8 = join(dir, IGNORE_FILE);
6591
+ if (!exists(path8)) return [];
6555
6592
  try {
6556
- const raw = readFile(path7);
6593
+ const raw = readFile(path8);
6557
6594
  const parsed = parseYaml4(raw);
6558
6595
  return Array.isArray(parsed) ? parsed : [];
6559
6596
  } catch {
@@ -6561,7 +6598,7 @@ function loadIgnores(dir) {
6561
6598
  }
6562
6599
  }
6563
6600
  function saveIgnore(dir, entry) {
6564
- const path7 = join(dir, IGNORE_FILE);
6601
+ const path8 = join(dir, IGNORE_FILE);
6565
6602
  const existing = loadIgnores(dir);
6566
6603
  const idx = existing.findIndex((e) => e.artifact_id === entry.artifact_id);
6567
6604
  if (idx >= 0) {
@@ -6569,17 +6606,17 @@ function saveIgnore(dir, entry) {
6569
6606
  } else {
6570
6607
  existing.push(entry);
6571
6608
  }
6572
- writeFile(path7, stringifyYaml2(existing));
6609
+ writeFile(path8, stringifyYaml2(existing));
6573
6610
  }
6574
6611
  function isIgnored(ignores, artifactId) {
6575
6612
  return ignores.find((e) => e.artifact_id === artifactId);
6576
6613
  }
6577
6614
  function removeIgnore(dir, artifactId) {
6578
- const path7 = join(dir, IGNORE_FILE);
6615
+ const path8 = join(dir, IGNORE_FILE);
6579
6616
  const existing = loadIgnores(dir);
6580
6617
  const filtered = existing.filter((e) => e.artifact_id !== artifactId);
6581
6618
  if (filtered.length === existing.length) return false;
6582
- writeFile(path7, stringifyYaml2(filtered));
6619
+ writeFile(path8, stringifyYaml2(filtered));
6583
6620
  return true;
6584
6621
  }
6585
6622
 
@@ -6601,11 +6638,11 @@ function moduleArtifactCoverage(dir, id) {
6601
6638
  };
6602
6639
  }
6603
6640
  function loadMappedModules(dir) {
6604
- const path7 = join(dir, DESCRIPTOR_PATH);
6605
- if (!exists(path7)) return [];
6641
+ const path8 = join(dir, DESCRIPTOR_PATH);
6642
+ if (!exists(path8)) return [];
6606
6643
  let parsed;
6607
6644
  try {
6608
- parsed = parseYaml5(readFile(path7));
6645
+ parsed = parseYaml5(readFile(path8));
6609
6646
  } catch {
6610
6647
  return [];
6611
6648
  }
@@ -6619,7 +6656,7 @@ function loadMappedModules(dir) {
6619
6656
  modules.push({
6620
6657
  id,
6621
6658
  name: typeof m.name === "string" ? m.name : void 0,
6622
- repoPath: typeof m.repoPath === "string" ? m.repoPath : "",
6659
+ repoPath: typeof m.repoPath === "string" ? m.repoPath : typeof m.path === "string" ? m.path : "",
6623
6660
  type: typeof m.type === "string" ? m.type : void 0,
6624
6661
  mainTechnology: typeof m.mainTechnology === "string" ? m.mainTechnology : void 0,
6625
6662
  owner: typeof m.owner === "string" ? m.owner : void 0,
@@ -6668,13 +6705,15 @@ function detectModuleStatus(coreDir, repoPath) {
6668
6705
  status: "configured",
6669
6706
  configured: true,
6670
6707
  role: "module",
6671
- moduleContext: exists(join(moduleDir2, "knowledge", "module", "module-context.md")),
6708
+ moduleContext: exists(join(moduleDir2, "knowledge", "tech", "module", "module-context.md")) || exists(join(moduleDir2, "knowledge", "module", "module-context.md")),
6672
6709
  currentState: exists(join(moduleDir2, "knowledge", "tech", "current-state.md")),
6673
6710
  codebase: exists(join(moduleDir2, "knowledge", "tech", "codebase.md"))
6674
6711
  };
6675
6712
  }
6676
6713
  function readModuleContext(coreDir, repoPath) {
6677
- const p2 = join(coreDir, repoPath, "knowledge", "module", "module-context.md");
6714
+ const newPath = join(coreDir, repoPath, "knowledge", "tech", "module", "module-context.md");
6715
+ const legacyPath = join(coreDir, repoPath, "knowledge", "module", "module-context.md");
6716
+ const p2 = exists(newPath) ? newPath : legacyPath;
6678
6717
  if (!exists(p2)) return null;
6679
6718
  try {
6680
6719
  return readFile(p2);
@@ -7085,7 +7124,7 @@ function buildGuardHistory(dir) {
7085
7124
  const d = hotspotDir(t.code_path);
7086
7125
  hotspotMap.set(d, (hotspotMap.get(d) ?? 0) + 1);
7087
7126
  }
7088
- const hotspots = [...hotspotMap.entries()].map(([path7, warnings]) => ({ path: path7, warnings })).sort((a, b) => b.warnings - a.warnings);
7127
+ const hotspots = [...hotspotMap.entries()].map(([path8, warnings]) => ({ path: path8, warnings })).sort((a, b) => b.warnings - a.warnings);
7089
7128
  return {
7090
7129
  available: true,
7091
7130
  total_runs: sorted.length,
@@ -7487,7 +7526,7 @@ function runIgnoreRemove(artifactId) {
7487
7526
 
7488
7527
  // src/commands/explain.ts
7489
7528
  import matter6 from "gray-matter";
7490
- import { parse as parseYaml10 } from "yaml";
7529
+ import { parse as parseYaml12 } from "yaml";
7491
7530
 
7492
7531
  // src/core/delivery-phase.ts
7493
7532
  function layer(layers, name) {
@@ -7653,10 +7692,10 @@ function assessPhase(input) {
7653
7692
 
7654
7693
  // src/core/capsule.ts
7655
7694
  import matter3 from "gray-matter";
7656
- import { parse as parseYaml9, stringify as stringifyYaml4 } from "yaml";
7695
+ import { parse as parseYaml10, stringify as stringifyYaml5 } from "yaml";
7657
7696
 
7658
7697
  // src/commands/modules-map.ts
7659
- import { parse as parseYaml8, stringify as stringifyYaml3 } from "yaml";
7698
+ import { parse as parseYaml9, stringify as stringifyYaml4 } from "yaml";
7660
7699
 
7661
7700
  // src/templates/registry.ts
7662
7701
  var QUALITY = "## Quality checklist";
@@ -9143,9 +9182,373 @@ function getTemplate(id) {
9143
9182
  return KADDO_TEMPLATES.find((t) => t.id === id);
9144
9183
  }
9145
9184
 
9185
+ // src/commands/modules-discover.ts
9186
+ import path5 from "path";
9187
+ import { parse as parseYaml8, stringify as stringifyYaml3 } from "yaml";
9188
+ var CONFIG_PATH3 = ".kaddo/config.yml";
9189
+ var MODULES_YML = ".kaddo/modules.yml";
9190
+ var MODULES_MD = "knowledge/tech/modules/modules.md";
9191
+ var IGNORED_DIRS = /* @__PURE__ */ new Set([
9192
+ ".git",
9193
+ "node_modules",
9194
+ "dist",
9195
+ "build",
9196
+ "coverage",
9197
+ "vendor",
9198
+ ".next",
9199
+ "out",
9200
+ "tmp",
9201
+ "temp",
9202
+ ".kaddo"
9203
+ ]);
9204
+ function readModulesYml(dir) {
9205
+ const p2 = join(dir, MODULES_YML);
9206
+ if (!exists(p2)) return { version: 1, system: "", workspace_roots: [".."], modules: [] };
9207
+ try {
9208
+ const parsed = parseYaml8(readFile(p2));
9209
+ return {
9210
+ version: parsed.version ?? 1,
9211
+ system: parsed.system ?? "",
9212
+ workspace_roots: parsed.workspace_roots ?? [".."],
9213
+ modules: Array.isArray(parsed.modules) ? parsed.modules : []
9214
+ };
9215
+ } catch {
9216
+ return { version: 1, system: "", workspace_roots: [".."], modules: [] };
9217
+ }
9218
+ }
9219
+ function writeModulesYml(dir, yml) {
9220
+ writeFile(join(dir, MODULES_YML), stringifyYaml3(yml));
9221
+ }
9222
+ function readRepoConfig(repoDir) {
9223
+ const configPath = join(repoDir, CONFIG_PATH3);
9224
+ if (!exists(configPath)) return null;
9225
+ try {
9226
+ return parseYaml8(readFile(configPath));
9227
+ } catch {
9228
+ return null;
9229
+ }
9230
+ }
9231
+ function hasKnowledgeFile(repoDir, ...segments) {
9232
+ return exists(join(repoDir, "knowledge", ...segments));
9233
+ }
9234
+ function hasModuleContext(repoDir) {
9235
+ return hasKnowledgeFile(repoDir, "tech", "module", "module-context.md") || hasKnowledgeFile(repoDir, "module", "module-context.md");
9236
+ }
9237
+ function hasCurrentState(repoDir) {
9238
+ return hasKnowledgeFile(repoDir, "tech", "current-state.md");
9239
+ }
9240
+ function hasCodebase(repoDir) {
9241
+ return hasKnowledgeFile(repoDir, "tech", "codebase.md");
9242
+ }
9243
+ function buildModuleContextPaths(repoDir) {
9244
+ const ctx = {};
9245
+ if (hasKnowledgeFile(repoDir, "tech", "module", "module-context.md")) {
9246
+ ctx.module = "knowledge/tech/module/module-context.md";
9247
+ } else if (hasKnowledgeFile(repoDir, "module", "module-context.md")) {
9248
+ ctx.module = "knowledge/module/module-context.md";
9249
+ }
9250
+ if (hasCurrentState(repoDir)) ctx.current_state = "knowledge/tech/current-state.md";
9251
+ if (hasCodebase(repoDir)) ctx.codebase = "knowledge/tech/codebase.md";
9252
+ return ctx;
9253
+ }
9254
+ function discoverModules(dir) {
9255
+ const config = loadConfig(dir);
9256
+ if (!config || !isCore(config)) return [];
9257
+ const sysName = systemName(config) ?? config.project.name;
9258
+ const roots = workspaceRoots(config);
9259
+ const existing = readModulesYml(dir);
9260
+ const seenIds = /* @__PURE__ */ new Set();
9261
+ const seenPaths = /* @__PURE__ */ new Set();
9262
+ const results = [];
9263
+ const coreAbsPath = path5.resolve(dir);
9264
+ for (const root of roots) {
9265
+ const rootAbs = path5.resolve(dir, root);
9266
+ if (!exists(rootAbs) || !isDir(rootAbs)) continue;
9267
+ let entries;
9268
+ try {
9269
+ entries = readDir(rootAbs);
9270
+ } catch {
9271
+ continue;
9272
+ }
9273
+ for (const entry of entries) {
9274
+ if (IGNORED_DIRS.has(entry)) continue;
9275
+ const candidateAbs = path5.resolve(rootAbs, entry);
9276
+ if (!isDir(candidateAbs)) continue;
9277
+ if (candidateAbs === coreAbsPath) continue;
9278
+ const relPath = path5.relative(dir, candidateAbs).replace(/\\/g, "/");
9279
+ const repoConfig = readRepoConfig(candidateAbs);
9280
+ if (!repoConfig) {
9281
+ results.push({
9282
+ id: entry,
9283
+ name: entry,
9284
+ path: relPath.startsWith(".") ? relPath : `./${relPath}`,
9285
+ parentSystem: "",
9286
+ status: "not_configured",
9287
+ eligibleForMapping: false,
9288
+ moduleContext: false,
9289
+ currentState: false,
9290
+ codebase: false
9291
+ });
9292
+ continue;
9293
+ }
9294
+ const project = repoConfig.project;
9295
+ const multirepo = repoConfig.multirepo;
9296
+ const module = repoConfig.module;
9297
+ const role = project?.role ?? multirepo?.role;
9298
+ if (role !== "module") {
9299
+ results.push({
9300
+ id: entry,
9301
+ name: project?.name ?? entry,
9302
+ path: relPath.startsWith(".") ? relPath : `./${relPath}`,
9303
+ parentSystem: "",
9304
+ status: "invalid",
9305
+ eligibleForMapping: false,
9306
+ warning: "Repository has Kaddo but is not configured as a module.",
9307
+ moduleContext: false,
9308
+ currentState: false,
9309
+ codebase: false
9310
+ });
9311
+ continue;
9312
+ }
9313
+ const modId = module?.id ?? entry;
9314
+ const modParent = multirepo?.parent_system ?? "";
9315
+ const modName = project?.name ?? entry;
9316
+ const modPath = relPath.startsWith(".") ? relPath : `./${relPath}`;
9317
+ if (modParent && modParent !== sysName) {
9318
+ results.push({
9319
+ id: modId,
9320
+ name: modName,
9321
+ path: modPath,
9322
+ parentSystem: modParent,
9323
+ status: "foreign_system",
9324
+ eligibleForMapping: false,
9325
+ warning: `parent system: ${modParent}`,
9326
+ moduleContext: hasModuleContext(candidateAbs),
9327
+ currentState: hasCurrentState(candidateAbs),
9328
+ codebase: hasCodebase(candidateAbs)
9329
+ });
9330
+ continue;
9331
+ }
9332
+ let status = "configured";
9333
+ let warning;
9334
+ let eligible = true;
9335
+ if (seenIds.has(modId)) {
9336
+ status = "duplicate";
9337
+ warning = `duplicate module id: ${modId}`;
9338
+ eligible = false;
9339
+ } else if (seenPaths.has(modPath)) {
9340
+ status = "duplicate";
9341
+ warning = `duplicate path: ${modPath}`;
9342
+ eligible = false;
9343
+ }
9344
+ seenIds.add(modId);
9345
+ seenPaths.add(modPath);
9346
+ results.push({
9347
+ id: modId,
9348
+ name: modName,
9349
+ path: modPath,
9350
+ parentSystem: modParent || sysName,
9351
+ status,
9352
+ eligibleForMapping: eligible,
9353
+ warning,
9354
+ moduleContext: hasModuleContext(candidateAbs),
9355
+ currentState: hasCurrentState(candidateAbs),
9356
+ codebase: hasCodebase(candidateAbs)
9357
+ });
9358
+ }
9359
+ }
9360
+ for (const m of existing.modules) {
9361
+ const absPath = path5.resolve(dir, m.path);
9362
+ if (!exists(absPath) && !results.some((r) => r.path === m.path)) {
9363
+ results.push({
9364
+ id: m.id,
9365
+ name: m.name,
9366
+ path: m.path,
9367
+ parentSystem: m.parent_system,
9368
+ status: "missing",
9369
+ eligibleForMapping: false,
9370
+ warning: "Previously registered but path no longer exists.",
9371
+ moduleContext: false,
9372
+ currentState: false,
9373
+ codebase: false
9374
+ });
9375
+ }
9376
+ }
9377
+ return results;
9378
+ }
9379
+ function applyDiscovery(dir, discovered) {
9380
+ const config = loadConfig(dir);
9381
+ const sysName = systemName(config) ?? config.project.name;
9382
+ const yml = readModulesYml(dir);
9383
+ yml.system = sysName;
9384
+ const eligible = discovered.filter((d) => d.eligibleForMapping && d.status === "configured");
9385
+ for (const d of eligible) {
9386
+ const existingIdx = yml.modules.findIndex((m) => m.id === d.id);
9387
+ const entry = {
9388
+ id: d.id,
9389
+ name: d.name,
9390
+ path: d.path,
9391
+ parent_system: d.parentSystem,
9392
+ status: d.status,
9393
+ context: buildModuleContextPaths(path5.resolve(dir, d.path))
9394
+ };
9395
+ if (existingIdx >= 0) {
9396
+ yml.modules[existingIdx] = entry;
9397
+ } else {
9398
+ yml.modules.push(entry);
9399
+ }
9400
+ }
9401
+ writeModulesYml(dir, yml);
9402
+ updateModulesMd(dir, yml);
9403
+ return { mapped: eligible.length };
9404
+ }
9405
+ function updateModulesMd(dir, yml) {
9406
+ const lines = [
9407
+ "---",
9408
+ "type: modules-map",
9409
+ `system: ${yml.system}`,
9410
+ "generated_by: kaddo-modules-discover",
9411
+ "template_version: 2",
9412
+ "---",
9413
+ "",
9414
+ `# ${yml.system} \u2014 Modules`,
9415
+ ""
9416
+ ];
9417
+ if (yml.modules.length === 0) {
9418
+ lines.push("_No modules mapped yet. Run `kaddo modules discover` to find sibling modules._");
9419
+ } else {
9420
+ for (const m of yml.modules) {
9421
+ lines.push(`## ${m.id}`);
9422
+ lines.push("");
9423
+ lines.push(`- Repository: \`${m.path}\``);
9424
+ lines.push(`- Status: ${m.status}`);
9425
+ lines.push(`- Parent system: ${m.parent_system}`);
9426
+ const ctxPaths = m.context ?? {};
9427
+ lines.push(`- Module context: ${ctxPaths.module ? "available" : "missing"}`);
9428
+ lines.push("");
9429
+ }
9430
+ }
9431
+ writeFile(join(dir, MODULES_MD), lines.join("\n"));
9432
+ }
9433
+ function runModulesValidate(dir = cwd()) {
9434
+ const yml = readModulesYml(dir);
9435
+ if (yml.modules.length === 0) {
9436
+ console.log("No modules registered in .kaddo/modules.yml.");
9437
+ return;
9438
+ }
9439
+ const config = loadConfig(dir);
9440
+ const sysName = systemName(config) ?? config.project.name;
9441
+ const legacy = detectLegacyPaths(dir);
9442
+ if (legacy.length > 0) {
9443
+ console.log("");
9444
+ console.log("Legacy multirepo knowledge paths detected.");
9445
+ console.log("");
9446
+ console.log("Recommended migration:");
9447
+ for (const l of legacy) {
9448
+ console.log(`- ${l.from}`);
9449
+ console.log(` \u2192 ${l.to}`);
9450
+ console.log("");
9451
+ }
9452
+ }
9453
+ console.log("");
9454
+ for (const m of yml.modules) {
9455
+ const absPath = path5.resolve(dir, m.path);
9456
+ if (!exists(absPath)) {
9457
+ console.log(`${m.id.padEnd(16)} missing`);
9458
+ continue;
9459
+ }
9460
+ const repoConfig = readRepoConfig(absPath);
9461
+ if (!repoConfig) {
9462
+ console.log(`${m.id.padEnd(16)} not_configured`);
9463
+ continue;
9464
+ }
9465
+ const project = repoConfig.project;
9466
+ const multirepo = repoConfig.multirepo;
9467
+ const role = project?.role ?? multirepo?.role;
9468
+ if (role !== "module") {
9469
+ console.log(`${m.id.padEnd(16)} invalid`);
9470
+ continue;
9471
+ }
9472
+ const modParent = multirepo?.parent_system;
9473
+ if (modParent && modParent !== sysName) {
9474
+ console.log(`${m.id.padEnd(16)} foreign_system`);
9475
+ continue;
9476
+ }
9477
+ console.log(`${m.id.padEnd(16)} valid`);
9478
+ }
9479
+ console.log("");
9480
+ }
9481
+ function detectLegacyPaths(dir) {
9482
+ const migrations = [];
9483
+ if (exists(join(dir, "knowledge", "system", "system-context.md"))) {
9484
+ migrations.push({ from: "knowledge/system/system-context.md", to: "knowledge/tech/system/system-context.md" });
9485
+ }
9486
+ if (exists(join(dir, "knowledge", "modules", "modules.md"))) {
9487
+ migrations.push({ from: "knowledge/modules/modules.md", to: "knowledge/tech/modules/modules.md" });
9488
+ }
9489
+ if (exists(join(dir, "knowledge", "module", "module-context.md"))) {
9490
+ migrations.push({ from: "knowledge/module/module-context.md", to: "knowledge/tech/module/module-context.md" });
9491
+ }
9492
+ return migrations;
9493
+ }
9494
+ async function runModulesDiscover(opts = {}) {
9495
+ const dir = cwd();
9496
+ intro2("kaddo modules discover");
9497
+ const config = loadConfig(dir);
9498
+ if (!config) {
9499
+ console.error("Kaddo is not initialized. Run `kaddo init` first.");
9500
+ process.exit(1);
9501
+ }
9502
+ if (!isCore(config)) {
9503
+ console.error("Module discovery is only available from a core repository.");
9504
+ process.exit(1);
9505
+ }
9506
+ const discovered = discoverModules(dir);
9507
+ if (discovered.length === 0) {
9508
+ log2.info("No sibling repositories found in workspace roots.");
9509
+ outro2("Discovery complete.");
9510
+ return;
9511
+ }
9512
+ console.log("");
9513
+ console.log("Workspace module discovery");
9514
+ console.log("");
9515
+ for (const d of discovered) {
9516
+ const icon = d.status === "configured" ? "\u2713" : d.status === "not_configured" ? "\u25CB" : d.status === "foreign_system" ? "!" : d.status === "duplicate" ? "!" : d.status === "missing" ? "\u2717" : "\u2717";
9517
+ console.log(`${icon} ${d.id}`);
9518
+ console.log(` ${d.path}`);
9519
+ console.log(` status: ${d.status}`);
9520
+ if (d.warning) console.log(` ${d.warning}`);
9521
+ console.log("");
9522
+ }
9523
+ const eligible = discovered.filter((d) => d.eligibleForMapping && d.status === "configured");
9524
+ console.log(`Eligible for mapping: ${eligible.length}`);
9525
+ if (eligible.length === 0) {
9526
+ outro2("Discovery complete.");
9527
+ return;
9528
+ }
9529
+ if (opts.apply) {
9530
+ const confirmed = await confirm2({
9531
+ message: `Map ${eligible.length} configured Kaddo modules?`,
9532
+ initialValue: true
9533
+ });
9534
+ if (!confirmed) {
9535
+ outro2("No changes applied.");
9536
+ return;
9537
+ }
9538
+ const result = applyDiscovery(dir, discovered);
9539
+ log2.success(`Mapped ${result.mapped} configured modules.`);
9540
+ log2.success("Updated .kaddo/modules.yml.");
9541
+ log2.success("Updated knowledge/tech/modules/modules.md.");
9542
+ outro2("Discovery applied.");
9543
+ } else {
9544
+ log2.info("Run `kaddo modules discover --apply` to persist eligible modules.");
9545
+ outro2("Discovery complete.");
9546
+ }
9547
+ }
9548
+
9146
9549
  // src/commands/modules-map.ts
9147
9550
  var DESCRIPTOR_PATH2 = ".kaddo/modules.yml";
9148
- var CONFIG_PATH3 = ".kaddo/config.yml";
9551
+ var CONFIG_PATH4 = ".kaddo/config.yml";
9149
9552
  var MODULE_TYPES = [
9150
9553
  "frontend",
9151
9554
  "backend",
@@ -9160,17 +9563,17 @@ function slugify2(name) {
9160
9563
  return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
9161
9564
  }
9162
9565
  function readModulesDescriptor(dir) {
9163
- const path7 = join(dir, DESCRIPTOR_PATH2);
9164
- if (!exists(path7)) return { version: 1, modules: [] };
9566
+ const path8 = join(dir, DESCRIPTOR_PATH2);
9567
+ if (!exists(path8)) return { version: 1, modules: [] };
9165
9568
  try {
9166
- const parsed = parseYaml8(readFile(path7));
9569
+ const parsed = parseYaml9(readFile(path8));
9167
9570
  return { version: parsed.version ?? 1, modules: parsed.modules ?? [] };
9168
9571
  } catch {
9169
9572
  return { version: 1, modules: [] };
9170
9573
  }
9171
9574
  }
9172
9575
  function writeModulesDescriptor(dir, descriptor) {
9173
- writeFile(join(dir, DESCRIPTOR_PATH2), stringifyYaml3(descriptor));
9576
+ writeFile(join(dir, DESCRIPTOR_PATH2), stringifyYaml4(descriptor));
9174
9577
  }
9175
9578
  function moduleDir(id) {
9176
9579
  return `knowledge/tech/modules/${id}`;
@@ -9301,70 +9704,74 @@ function mapModule(dir, input) {
9301
9704
  }
9302
9705
  return { module: mod, written, skipped, alreadyRegistered };
9303
9706
  }
9304
- async function runModulesMap(dir = cwd()) {
9707
+ async function runModulesMap(dir = cwd(), modulePath) {
9305
9708
  intro2("kaddo modules map");
9306
- if (!exists(join(dir, CONFIG_PATH3))) {
9709
+ if (!exists(join(dir, CONFIG_PATH4))) {
9307
9710
  console.error("Kaddo is not initialized in this project.");
9308
9711
  console.error("Run `kaddo init` first.");
9309
9712
  process.exit(1);
9310
9713
  }
9311
- log2.info("Register a secondary repository as a module of this system.");
9714
+ let repoPath;
9715
+ if (modulePath) {
9716
+ repoPath = modulePath;
9717
+ } else {
9718
+ repoPath = await text2({
9719
+ message: "Repository path (relative to this repo)",
9720
+ placeholder: "e.g. ../frontend",
9721
+ validate: (v) => v.trim().length === 0 ? "Repository path is required." : void 0
9722
+ });
9723
+ }
9724
+ const targetDir = join(dir, repoPath.trim());
9725
+ if (!exists(targetDir)) {
9726
+ log2.warn(`Path "${repoPath.trim()}" does not exist.`);
9727
+ } else {
9728
+ const st = detectModuleStatus(dir, repoPath.trim());
9729
+ if (st.status === "not_configured") {
9730
+ log2.warn(`${repoPath.trim()} does not have Kaddo configured. Run \`kaddo init\` inside that repository first.`);
9731
+ } else if (st.status === "invalid") {
9732
+ log2.warn(`${repoPath.trim()} has Kaddo but is not configured as a module.`);
9733
+ }
9734
+ }
9312
9735
  const name = await text2({
9313
9736
  message: "Module name",
9314
9737
  placeholder: "e.g. Frontend Web",
9315
9738
  validate: (v) => v.trim().length === 0 ? "Name is required." : void 0
9316
9739
  });
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
9740
  const type = await select2({
9323
9741
  message: "Module type",
9324
9742
  options: MODULE_TYPES.map((t) => ({ value: t, label: t }))
9325
9743
  });
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
9744
  const result = mapModule(dir, {
9342
9745
  name,
9343
9746
  repoPath,
9344
- type,
9345
- mainTechnology,
9346
- owner,
9347
- capabilities: capabilitiesRaw.split(",").map((c) => c.trim()).filter(Boolean)
9747
+ type
9348
9748
  });
9349
9749
  for (const p2 of result.written) log2.success(`Created ${p2}`);
9350
9750
  for (const p2 of result.skipped) log2.info(`Kept existing ${p2}`);
9351
9751
  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
- );
9752
+ outro2("Module mapped.");
9355
9753
  }
9356
9754
  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.");
9755
+ const yml = readModulesYml(dir);
9756
+ if (yml.modules.length === 0) {
9757
+ console.log("");
9758
+ console.log("No modules are mapped for this core repository.");
9759
+ console.log("");
9760
+ console.log("Run:");
9761
+ console.log(" kaddo modules discover");
9762
+ console.log("");
9763
+ console.log("Or map one manually:");
9764
+ console.log(" kaddo modules map <path>");
9765
+ console.log("");
9360
9766
  return;
9361
9767
  }
9362
9768
  console.log("");
9363
9769
  console.log("Mapped modules:");
9364
- for (const m of descriptor.modules) {
9365
- const st = detectModuleStatus(dir, m.repoPath);
9770
+ for (const m of yml.modules) {
9771
+ const mPath = m.path ?? m.repoPath ?? "";
9772
+ const st = detectModuleStatus(dir, mPath);
9366
9773
  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}`);
9774
+ console.log(` ${m.id.padEnd(16)} ${statusLabel.padEnd(18)} ${mPath}`);
9368
9775
  if (st.warning) console.log(` \u26A0 ${st.warning}`);
9369
9776
  }
9370
9777
  console.log("");
@@ -9505,14 +9912,14 @@ function loadExternalRegistry(dir) {
9505
9912
  const p2 = join(dir, EXTERNAL_PATH);
9506
9913
  if (!exists(p2)) return [];
9507
9914
  try {
9508
- const parsed = parseYaml9(readFile(p2));
9915
+ const parsed = parseYaml10(readFile(p2));
9509
9916
  return Array.isArray(parsed?.external) ? parsed.external : [];
9510
9917
  } catch {
9511
9918
  return [];
9512
9919
  }
9513
9920
  }
9514
9921
  function serializeExternalRegistry(entries) {
9515
- return stringifyYaml4({ external: entries });
9922
+ return stringifyYaml5({ external: entries });
9516
9923
  }
9517
9924
  function sectionList(md, title) {
9518
9925
  const re = new RegExp(`^##\\s+${title}\\s*$`, "im");
@@ -9539,7 +9946,7 @@ function sectionParagraph(md, title) {
9539
9946
  }
9540
9947
  return "";
9541
9948
  }
9542
- function parseCapsule(id, path7, md) {
9949
+ function parseCapsule(id, path8, md) {
9543
9950
  const { data } = matter3(md);
9544
9951
  const updatedAt = data.updated_at ? String(data.updated_at) : void 0;
9545
9952
  let ageDays = null;
@@ -9549,7 +9956,7 @@ function parseCapsule(id, path7, md) {
9549
9956
  }
9550
9957
  return {
9551
9958
  id,
9552
- path: path7,
9959
+ path: path8,
9553
9960
  system: data.system ? String(data.system) : id,
9554
9961
  owner: data.owner ? String(data.owner) : void 0,
9555
9962
  updatedAt,
@@ -9635,9 +10042,9 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
9635
10042
  ];
9636
10043
  const presentLayers = [];
9637
10044
  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 });
10045
+ const path8 = layer2.files.map((f) => `${KNOWLEDGE3}/${f}`).find((rel) => exists(join(dir, rel)));
10046
+ if (path8) {
10047
+ addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path: path8 });
9641
10048
  presentLayers.push(layer2.id);
9642
10049
  }
9643
10050
  }
@@ -10965,6 +11372,9 @@ function buildSharedFileStatuses(statuses) {
10965
11372
  }));
10966
11373
  }
10967
11374
 
11375
+ // src/core/next-step.ts
11376
+ import { parse as parseYaml11 } from "yaml";
11377
+
10968
11378
  // src/core/decisions.ts
10969
11379
  var CANDIDATES_DISCOVERY = "knowledge/tech/discovery/decision-candidates.md";
10970
11380
  var CANDIDATES_LEGACY = "knowledge/tech/decision-candidates.md";
@@ -11080,6 +11490,14 @@ function buildDeliveryState(dir) {
11080
11490
  adapters_installed: adapters.length
11081
11491
  };
11082
11492
  }
11493
+ function hasRegisteredModules(modulesYmlPath) {
11494
+ try {
11495
+ const parsed = parseYaml11(readFile(modulesYmlPath));
11496
+ return Array.isArray(parsed?.modules) && parsed.modules.length > 0;
11497
+ } catch {
11498
+ return false;
11499
+ }
11500
+ }
11083
11501
  function safeRead(p2) {
11084
11502
  try {
11085
11503
  return readFile(p2);
@@ -11130,11 +11548,12 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
11130
11548
  const state = config.project.state;
11131
11549
  const q = (rel) => analyzeKnowledgeArtifact(dir, rel);
11132
11550
  const moduleRepo = isModule(config);
11133
- const MC = "knowledge/module/module-context.md";
11551
+ const coreRepo = isCore(config);
11552
+ const MC = exists(join(dir, "knowledge/tech/module/module-context.md")) ? "knowledge/tech/module/module-context.md" : "knowledge/module/module-context.md";
11134
11553
  if (moduleRepo) {
11135
11554
  const qMC = q(MC);
11136
11555
  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." };
11556
+ 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
11557
  }
11139
11558
  if ((state === "pre-ai" || state === "legacy") && !exists(join(dir, ".kaddo", "scan.json"))) {
11140
11559
  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." };
@@ -11183,9 +11602,15 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
11183
11602
  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." };
11184
11603
  }
11185
11604
  const qBusiness = q(B), qProduct = q(P), qCap = q(CAP), qCurrentState = q(CS), qCodebase = q(CB);
11186
- if (qBusiness === "missing" || qProduct === "missing") {
11605
+ if (!coreRepo && (qBusiness === "missing" || qProduct === "missing")) {
11187
11606
  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
11607
  }
11608
+ if (coreRepo) {
11609
+ const modulesYmlPath = join(dir, ".kaddo", "modules.yml");
11610
+ if (!exists(modulesYmlPath) || !hasRegisteredModules(modulesYmlPath)) {
11611
+ 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." };
11612
+ }
11613
+ }
11189
11614
  const ctx = buildCodexAdapterContext(dir);
11190
11615
  if (!ctx.hasAgents) {
11191
11616
  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." };
@@ -11202,10 +11627,10 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
11202
11627
  const discovery = state === "pre-ai" || state === "legacy";
11203
11628
  const resolveAgent = (agent) => {
11204
11629
  const file = agent.endsWith(".md") ? agent : `${agent}.md`;
11205
- const path7 = agentInstallPath(file);
11206
- const installed = isFile(join(dir, path7));
11630
+ const path8 = agentInstallPath(file);
11631
+ const installed = isFile(join(dir, path8));
11207
11632
  const group = agentGroupOf(file);
11208
- return { agentPath: path7, agentInstalled: installed, installCommand: installed ? void 0 : `kaddo add agents --group ${group}` };
11633
+ return { agentPath: path8, agentInstalled: installed, installCommand: installed ? void 0 : `kaddo add agents --group ${group}` };
11209
11634
  };
11210
11635
  const refine = (id, agent, target, quality, verb = "complete") => {
11211
11636
  const ar = resolveAgent(agent);
@@ -12088,7 +12513,8 @@ function mapNextStepId(id) {
12088
12513
  "refine-module-context": "refine-work-item",
12089
12514
  "init-module-context": "enable-kaddo",
12090
12515
  "module-ready": "prepare-implementation",
12091
- "validate-work-item-modules": "prepare-implementation"
12516
+ "validate-work-item-modules": "prepare-implementation",
12517
+ "modules-discover": "enable-kaddo"
12092
12518
  };
12093
12519
  return MAP[id] ?? id;
12094
12520
  }
@@ -12759,7 +13185,7 @@ function renderExplanationAgent(exp) {
12759
13185
 
12760
13186
  // src/commands/explain.ts
12761
13187
  var ARCH_DIR5 = "knowledge";
12762
- var CONFIG_PATH4 = ".kaddo/config.yml";
13188
+ var CONFIG_PATH5 = ".kaddo/config.yml";
12763
13189
  function readKnowledge(dir) {
12764
13190
  const knowledgePath = join(dir, ARCH_DIR5, "knowledge.md");
12765
13191
  if (!exists(knowledgePath)) return null;
@@ -12783,10 +13209,10 @@ function readRoadmap(dir) {
12783
13209
  }
12784
13210
  }
12785
13211
  function readConfig(dir) {
12786
- const configPath = join(dir, CONFIG_PATH4);
13212
+ const configPath = join(dir, CONFIG_PATH5);
12787
13213
  if (!exists(configPath)) return {};
12788
13214
  try {
12789
- return parseYaml10(readFile(configPath));
13215
+ return parseYaml12(readFile(configPath));
12790
13216
  } catch {
12791
13217
  return {};
12792
13218
  }
@@ -12897,7 +13323,7 @@ function explainForAgent(dir, artifacts, opts) {
12897
13323
  console.log(JSON.stringify(output, null, 2));
12898
13324
  }
12899
13325
  function explainProject(dir, mode) {
12900
- if (!exists(join(dir, CONFIG_PATH4))) {
13326
+ if (!exists(join(dir, CONFIG_PATH5))) {
12901
13327
  console.error("No .kaddo/config.yml found. Run `kaddo init` first.");
12902
13328
  process.exit(1);
12903
13329
  }
@@ -13969,7 +14395,7 @@ function renderUnderstandTerminal(plan) {
13969
14395
  }
13970
14396
 
13971
14397
  // src/core/delivery.ts
13972
- import { parse as parseYaml11 } from "yaml";
14398
+ import { parse as parseYaml13 } from "yaml";
13973
14399
  function slugify4(s) {
13974
14400
  return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
13975
14401
  }
@@ -14203,7 +14629,7 @@ function runUnderstand() {
14203
14629
  }
14204
14630
 
14205
14631
  // src/core/classifier.ts
14206
- import path5 from "path";
14632
+ import path6 from "path";
14207
14633
  var MIGRATION_PATTERNS = [
14208
14634
  /supabase\/migrations\//,
14209
14635
  /prisma\/migrations\//,
@@ -14242,7 +14668,7 @@ var DEP_PATTERNS = [
14242
14668
  /^pom\.xml$/
14243
14669
  ];
14244
14670
  function normalizePath2(p2) {
14245
- return p2.split(path5.sep).join("/");
14671
+ return p2.split(path6.sep).join("/");
14246
14672
  }
14247
14673
  function matchesAny2(file, patterns) {
14248
14674
  const normalized = normalizePath2(file);
@@ -14420,14 +14846,14 @@ async function runClassify(opts = {}) {
14420
14846
  }
14421
14847
 
14422
14848
  // src/commands/status.ts
14423
- import { parse as parseYaml12 } from "yaml";
14849
+ import { parse as parseYaml14 } from "yaml";
14424
14850
  var ARCH_DIR8 = "knowledge";
14425
- var CONFIG_PATH5 = ".kaddo/config.yml";
14851
+ var CONFIG_PATH6 = ".kaddo/config.yml";
14426
14852
  function loadConfig3(dir) {
14427
- const p2 = join(dir, CONFIG_PATH5);
14853
+ const p2 = join(dir, CONFIG_PATH6);
14428
14854
  if (!exists(p2)) return {};
14429
14855
  try {
14430
- return parseYaml12(readFile(p2));
14856
+ return parseYaml14(readFile(p2));
14431
14857
  } catch {
14432
14858
  return {};
14433
14859
  }
@@ -14629,13 +15055,13 @@ function runHistory(opts = {}) {
14629
15055
  }
14630
15056
 
14631
15057
  // src/commands/add.ts
14632
- import { parse as parseYaml13, stringify as stringifyYaml5 } from "yaml";
14633
- var CONFIG_PATH6 = ".kaddo/config.yml";
15058
+ import { parse as parseYaml15, stringify as stringifyYaml6 } from "yaml";
15059
+ var CONFIG_PATH7 = ".kaddo/config.yml";
14634
15060
  function readProjectState(dir) {
14635
- const configPath = join(dir, CONFIG_PATH6);
15061
+ const configPath = join(dir, CONFIG_PATH7);
14636
15062
  if (!exists(configPath)) return void 0;
14637
15063
  try {
14638
- const config = parseYaml13(readFile(configPath));
15064
+ const config = parseYaml15(readFile(configPath));
14639
15065
  return config.project?.state;
14640
15066
  } catch {
14641
15067
  return void 0;
@@ -14656,23 +15082,23 @@ function selectSkillModuleFiles(files, opts) {
14656
15082
  return { files: selected, label };
14657
15083
  }
14658
15084
  function markModuleInstalled(dir, configKey, moduleName) {
14659
- const configPath = join(dir, CONFIG_PATH6);
15085
+ const configPath = join(dir, CONFIG_PATH7);
14660
15086
  if (!exists(configPath)) return;
14661
15087
  try {
14662
- const config = parseYaml13(readFile(configPath));
15088
+ const config = parseYaml15(readFile(configPath));
14663
15089
  config[configKey] = { installed: true, installed_at: (/* @__PURE__ */ new Date()).toISOString().split("T")[0] };
14664
15090
  const modules = config.modules ?? [];
14665
15091
  if (!modules.includes(moduleName)) modules.push(moduleName);
14666
15092
  config.modules = modules;
14667
- writeFile(configPath, stringifyYaml5(config));
15093
+ writeFile(configPath, stringifyYaml6(config));
14668
15094
  } catch {
14669
15095
  }
14670
15096
  }
14671
15097
  function isModuleInstalled(dir, configKey) {
14672
- const configPath = join(dir, CONFIG_PATH6);
15098
+ const configPath = join(dir, CONFIG_PATH7);
14673
15099
  if (!exists(configPath)) return false;
14674
15100
  try {
14675
- const config = parseYaml13(readFile(configPath));
15101
+ const config = parseYaml15(readFile(configPath));
14676
15102
  const moduleConfig = config[configKey];
14677
15103
  return moduleConfig?.installed === true;
14678
15104
  } catch {
@@ -14696,7 +15122,7 @@ function runAdd(moduleName, opts = {}, dir = cwd()) {
14696
15122
  process.exit(1);
14697
15123
  }
14698
15124
  intro2(`kaddo add ${mod.name}`);
14699
- if (!exists(join(dir, CONFIG_PATH6))) {
15125
+ if (!exists(join(dir, CONFIG_PATH7))) {
14700
15126
  console.error("Kaddo is not initialized in this project.");
14701
15127
  console.error("Run `kaddo init` first.");
14702
15128
  process.exit(1);
@@ -14901,11 +15327,11 @@ function applyOwnership(raw, globs, mode = "replace") {
14901
15327
  }
14902
15328
 
14903
15329
  // src/commands/owners.ts
14904
- var CONFIG_PATH7 = ".kaddo/config.yml";
15330
+ var CONFIG_PATH8 = ".kaddo/config.yml";
14905
15331
  var CUSTOM_GLOB = "__custom__";
14906
15332
  function runOwners(opts) {
14907
15333
  const dir = cwd();
14908
- if (!exists(join(dir, CONFIG_PATH7))) {
15334
+ if (!exists(join(dir, CONFIG_PATH8))) {
14909
15335
  console.error("No .kaddo/config.yml found. Run `kaddo init` first.");
14910
15336
  process.exit(1);
14911
15337
  }
@@ -14995,7 +15421,7 @@ async function chooseGlobs(suggested) {
14995
15421
  }
14996
15422
  async function runOwnersSuggest() {
14997
15423
  const dir = cwd();
14998
- if (!exists(join(dir, CONFIG_PATH7))) {
15424
+ if (!exists(join(dir, CONFIG_PATH8))) {
14999
15425
  console.error("No .kaddo/config.yml found. Run `kaddo init` first.");
15000
15426
  process.exit(1);
15001
15427
  }
@@ -15070,113 +15496,6 @@ ${globs.map((g) => ` - ${g}`).join("\n")}`);
15070
15496
  outro2("Ownership declared.");
15071
15497
  }
15072
15498
 
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
15499
  // src/core/bootstrap-templates.ts
15181
15500
  function fm(type, state) {
15182
15501
  return `---
@@ -15608,7 +15927,7 @@ function baselineTemplate(kind, state) {
15608
15927
  }
15609
15928
 
15610
15929
  // src/commands/bootstrap.ts
15611
- var CONFIG_PATH8 = ".kaddo/config.yml";
15930
+ var CONFIG_PATH9 = ".kaddo/config.yml";
15612
15931
  var FILE_TARGETS = [
15613
15932
  { path: "knowledge/business/business.md", kind: "business" },
15614
15933
  { path: "knowledge/product/product.md", kind: "product" },
@@ -15665,7 +15984,7 @@ function bootstrap(dir) {
15665
15984
  }
15666
15985
  async function runBootstrap(dir = cwd()) {
15667
15986
  intro2("kaddo bootstrap");
15668
- if (!exists(join(dir, CONFIG_PATH8))) {
15987
+ if (!exists(join(dir, CONFIG_PATH9))) {
15669
15988
  console.error("Kaddo is not initialized in this project.");
15670
15989
  console.error("Run `kaddo init` first.");
15671
15990
  process.exit(1);
@@ -16257,7 +16576,7 @@ function runReportImpact(opts = {}) {
16257
16576
  }
16258
16577
 
16259
16578
  // src/core/savings.ts
16260
- import { parse as parseYaml15 } from "yaml";
16579
+ import { parse as parseYaml16 } from "yaml";
16261
16580
  var DEFAULT_ASSUMPTIONS = {
16262
16581
  currency: "USD",
16263
16582
  hourly_cost: 40,
@@ -16275,7 +16594,7 @@ function loadAssumptions(dir) {
16275
16594
  const p2 = join(dir, SAVINGS_PATH);
16276
16595
  if (!exists(p2)) return { assumptions: { ...DEFAULT_ASSUMPTIONS }, source: "default" };
16277
16596
  try {
16278
- const raw = parseYaml15(readFile(p2));
16597
+ const raw = parseYaml16(readFile(p2));
16279
16598
  const a = raw?.assumptions ?? {};
16280
16599
  const t = raw?.team ?? {};
16281
16600
  const num = (v, d) => typeof v === "number" && Number.isFinite(v) ? v : d;
@@ -17057,7 +17376,7 @@ function runAssetsUpdate(kind, opts = {}) {
17057
17376
 
17058
17377
  // src/commands/ready.ts
17059
17378
  import fs4 from "fs";
17060
- import path6 from "path";
17379
+ import path7 from "path";
17061
17380
  import matter11 from "gray-matter";
17062
17381
  function validateReadiness(content, frontmatter) {
17063
17382
  const warnings = [];
@@ -17120,10 +17439,10 @@ function transitionToReady(filePath) {
17120
17439
  const posix = filePath.replace(/\\/g, "/");
17121
17440
  let newPath = filePath;
17122
17441
  if (posix.includes("/work-items/draft/")) {
17123
- const readyDir = path6.dirname(filePath).replace(/[/\\]draft$/, path6.sep + "ready");
17442
+ const readyDir = path7.dirname(filePath).replace(/[/\\]draft$/, path7.sep + "ready");
17124
17443
  ensureDir(readyDir);
17125
- const fileName = path6.basename(filePath);
17126
- newPath = path6.join(readyDir, fileName);
17444
+ const fileName = path7.basename(filePath);
17445
+ newPath = path7.join(readyDir, fileName);
17127
17446
  }
17128
17447
  return { newPath, newContent };
17129
17448
  }
@@ -17194,8 +17513,8 @@ async function runReady(id, opts = {}) {
17194
17513
  fs4.unlinkSync(wi.filePath);
17195
17514
  log2.success(`Updated status: ready`);
17196
17515
  log2.success(`Moved file:`);
17197
- log2.info(` ${path6.relative(dir, wi.filePath)}`);
17198
- log2.info(` \u2192 ${path6.relative(dir, newPath)}`);
17516
+ log2.info(` ${path7.relative(dir, wi.filePath)}`);
17517
+ log2.info(` \u2192 ${path7.relative(dir, newPath)}`);
17199
17518
  } else {
17200
17519
  writeFile(wi.filePath, newContent);
17201
17520
  log2.success(`Updated status: ready`);
@@ -17229,8 +17548,8 @@ var capsuleCmd = program.command("capsule").description("Export this project as
17229
17548
  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
17549
  runCapsuleExport(opts);
17231
17550
  });
17232
- capsuleCmd.command("add <path>").description("Register an external Knowledge Capsule as project context (.kaddo/external.yml)").action((path7) => {
17233
- runCapsuleAdd(path7);
17551
+ capsuleCmd.command("add <path>").description("Register an external Knowledge Capsule as project context (.kaddo/external.yml)").action((path8) => {
17552
+ runCapsuleAdd(path8);
17234
17553
  });
17235
17554
  var graphCmd = program.command("graph").description("Export the lightweight, file-based knowledge graph of the project");
17236
17555
  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 +17647,23 @@ program.command("owners [action]").description("List domain owners, or run `owne
17328
17647
  runOwners(opts);
17329
17648
  }
17330
17649
  });
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);
17650
+ program.command("module").description("(deprecated) Use `kaddo modules` instead").allowUnknownOption().action(() => {
17651
+ console.log("Unknown command `module`.");
17652
+ console.log("Did you mean `kaddo modules list`?");
17333
17653
  });
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();
17654
+ var modulesCmd = program.command("modules").description("Discover, map, list and validate multirepo modules");
17655
+ modulesCmd.command("discover").description("Discover sibling repositories configured as Kaddo modules").option("--apply", "Persist discovered modules to .kaddo/modules.yml").action(async (opts) => {
17656
+ await runModulesDiscover(opts);
17337
17657
  });
17338
- modulesCmd.command("list").description("List mapped modules").action(() => {
17658
+ modulesCmd.command("map [path]").description("Register a repository as a module manually").action(async (modulePath) => {
17659
+ await runModulesMap(void 0, modulePath);
17660
+ });
17661
+ modulesCmd.command("list").description("List mapped modules from .kaddo/modules.yml").action(() => {
17339
17662
  runModulesList();
17340
17663
  });
17664
+ modulesCmd.command("validate").description("Validate registered modules against their current configuration").action(() => {
17665
+ runModulesValidate();
17666
+ });
17341
17667
  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
17668
  runAdd(moduleName ?? "", { all: opts.all, group: opts.group });
17343
17669
  });