@kaddo/mcp 3.90.2 → 3.91.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 +68 -21
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -10472,6 +10472,7 @@ import path32 from "path";
10472
10472
  import crypto2 from "crypto";
10473
10473
  import matter72 from "gray-matter";
10474
10474
  import crypto3 from "crypto";
10475
+ import fs4 from "fs";
10475
10476
  import matter82 from "gray-matter";
10476
10477
  import matter92 from "gray-matter";
10477
10478
  import { parse as parseYaml92, stringify as stringifyYaml5 } from "yaml";
@@ -16581,6 +16582,12 @@ function findDuplicateByHash(dir, hash) {
16581
16582
  }
16582
16583
  return void 0;
16583
16584
  }
16585
+ function findExistingById(dir, id) {
16586
+ const items = discoverWorkItems2(dir);
16587
+ const match = items.find((item) => (item.id || item.title) === id);
16588
+ if (!match) return void 0;
16589
+ return { workItemId: match.id || match.title, path: match.relPath, filePath: match.filePath };
16590
+ }
16584
16591
  function importWorkItem(dir, opts) {
16585
16592
  const content = opts.content;
16586
16593
  if (!content?.trim()) throw new ImportError("EMPTY_INPUT", "Import content is empty.");
@@ -16609,6 +16616,31 @@ function importWorkItem(dir, opts) {
16609
16616
  sourceHash,
16610
16617
  sourceFormat: parsed.format
16611
16618
  });
16619
+ const importedId = typeof parsed.candidateFields.id === "string" ? parsed.candidateFields.id.trim() : void 0;
16620
+ let replacedWorkItem;
16621
+ if (importedId && /^WI-\d+$/.test(importedId)) {
16622
+ const existing = findExistingById(dir, importedId);
16623
+ if (existing) {
16624
+ if (!opts.onConflict) {
16625
+ return {
16626
+ created: false,
16627
+ workItemId: existing.workItemId,
16628
+ path: existing.path,
16629
+ status: "draft",
16630
+ source: opts.source,
16631
+ sourceFormat: parsed.format,
16632
+ sourceHash,
16633
+ executed: false,
16634
+ conflict: { importedId, existingPath: existing.path },
16635
+ ...normalized.discardedFields.length > 0 ? { discardedFields: normalized.discardedFields } : {}
16636
+ };
16637
+ }
16638
+ if (opts.onConflict === "replace") {
16639
+ fs4.rmSync(existing.filePath, { force: true });
16640
+ replacedWorkItem = existing.workItemId;
16641
+ }
16642
+ }
16643
+ }
16612
16644
  const result = createWorkItem(dir, {
16613
16645
  intent: normalized.intent,
16614
16646
  type: normalized.type,
@@ -16630,7 +16662,8 @@ function importWorkItem(dir, opts) {
16630
16662
  sourceHash,
16631
16663
  executed: false,
16632
16664
  refinementHandoff,
16633
- ...normalized.discardedFields.length > 0 ? { discardedFields: normalized.discardedFields } : {}
16665
+ ...normalized.discardedFields.length > 0 ? { discardedFields: normalized.discardedFields } : {},
16666
+ ...replacedWorkItem ? { replacedWorkItem } : {}
16634
16667
  };
16635
16668
  }
16636
16669
  var ImportError = class extends Error {
@@ -17396,12 +17429,24 @@ function listGraphHints(root, filter = {}) {
17396
17429
  }
17397
17430
  function importWorkItemTool(root, args) {
17398
17431
  const source = args.source === "cli" || args.source === "admin" ? args.source : "chat";
17432
+ const onConflict = args.onConflict === "replace" || args.onConflict === "new-id" ? args.onConflict : void 0;
17399
17433
  try {
17400
17434
  const result = importWorkItem(root, {
17401
17435
  content: args.content,
17402
17436
  source,
17403
- type: args.type
17437
+ type: args.type,
17438
+ onConflict
17404
17439
  });
17440
+ if (result.conflict) {
17441
+ return ok({
17442
+ created: false,
17443
+ conflict: true,
17444
+ importedId: result.conflict.importedId,
17445
+ existingPath: result.conflict.existingPath,
17446
+ message: `The imported content has id "${result.conflict.importedId}" which already exists. Call again with onConflict: "replace" to delete the existing Work Item and create the new one, or onConflict: "new-id" to assign a new consecutive ID.`,
17447
+ executed: false
17448
+ });
17449
+ }
17405
17450
  return ok({
17406
17451
  created: result.created,
17407
17452
  workItemId: result.workItemId,
@@ -17412,7 +17457,8 @@ function importWorkItemTool(root, args) {
17412
17457
  duplicateOf: result.duplicateOf,
17413
17458
  executed: false,
17414
17459
  refinementHandoff: result.refinementHandoff ? { agent: result.refinementHandoff.recommendedAgent, skill: result.refinementHandoff.recommendedSkill, text: result.refinementHandoff.text } : void 0,
17415
- ...result.discardedFields?.length ? { discardedFields: result.discardedFields } : {}
17460
+ ...result.discardedFields?.length ? { discardedFields: result.discardedFields } : {},
17461
+ ...result.replacedWorkItem ? { replacedWorkItem: result.replacedWorkItem } : {}
17416
17462
  });
17417
17463
  } catch (err) {
17418
17464
  if (err instanceof ImportError) return fail(`[${err.code}] ${err.message}`);
@@ -17421,7 +17467,7 @@ function importWorkItemTool(root, args) {
17421
17467
  }
17422
17468
 
17423
17469
  // src/multirepo.ts
17424
- import fs4 from "fs";
17470
+ import fs5 from "fs";
17425
17471
  import path4 from "path";
17426
17472
  import matter12 from "gray-matter";
17427
17473
  import { parse as parseYaml11, stringify as stringifyYaml6 } from "yaml";
@@ -17446,21 +17492,21 @@ function readModuleFile(root, repoPath, relFile) {
17446
17492
  const moduleDir = path4.resolve(root, repoPath);
17447
17493
  const abs = path4.resolve(moduleDir, relFile);
17448
17494
  if (!abs.startsWith(moduleDir)) return null;
17449
- if (!fs4.existsSync(abs) || !fs4.statSync(abs).isFile()) return null;
17450
- return fs4.readFileSync(abs, "utf-8");
17495
+ if (!fs5.existsSync(abs) || !fs5.statSync(abs).isFile()) return null;
17496
+ return fs5.readFileSync(abs, "utf-8");
17451
17497
  }
17452
17498
  function detectModuleStatus2(root, repoPath) {
17453
17499
  const moduleDir = path4.resolve(root, repoPath);
17454
- if (!fs4.existsSync(moduleDir)) {
17500
+ if (!fs5.existsSync(moduleDir)) {
17455
17501
  return { status: "missing", configured: false, moduleContext: false, currentState: false, codebase: false, warning: `Directory \`${repoPath}\` does not exist.` };
17456
17502
  }
17457
17503
  const configPath = path4.join(moduleDir, ".kaddo", "config.yml");
17458
- if (!fs4.existsSync(configPath)) {
17504
+ if (!fs5.existsSync(configPath)) {
17459
17505
  return { status: "not_configured", configured: false, moduleContext: false, currentState: false, codebase: false, warning: `Run \`kaddo init\` inside \`${repoPath}\` and select multirepo \u2192 module.` };
17460
17506
  }
17461
17507
  let parsed = null;
17462
17508
  try {
17463
- parsed = parseYaml11(fs4.readFileSync(configPath, "utf-8"));
17509
+ parsed = parseYaml11(fs5.readFileSync(configPath, "utf-8"));
17464
17510
  } catch {
17465
17511
  }
17466
17512
  const role = parsed?.project?.role ?? parsed?.multirepo?.role;
@@ -17471,14 +17517,14 @@ function detectModuleStatus2(root, repoPath) {
17471
17517
  status: "configured",
17472
17518
  configured: true,
17473
17519
  role: "module",
17474
- moduleContext: fs4.existsSync(path4.join(moduleDir, "knowledge", "tech", "module", "module-context.md")) || fs4.existsSync(path4.join(moduleDir, "knowledge", "module", "module-context.md")),
17475
- currentState: fs4.existsSync(path4.join(moduleDir, "knowledge", "tech", "current-state.md")),
17476
- codebase: fs4.existsSync(path4.join(moduleDir, "knowledge", "tech", "codebase.md"))
17520
+ moduleContext: fs5.existsSync(path4.join(moduleDir, "knowledge", "tech", "module", "module-context.md")) || fs5.existsSync(path4.join(moduleDir, "knowledge", "module", "module-context.md")),
17521
+ currentState: fs5.existsSync(path4.join(moduleDir, "knowledge", "tech", "current-state.md")),
17522
+ codebase: fs5.existsSync(path4.join(moduleDir, "knowledge", "tech", "codebase.md"))
17477
17523
  };
17478
17524
  }
17479
17525
  function resolveModuleContextPath(root, repoPath) {
17480
17526
  const moduleDir = path4.resolve(root, repoPath);
17481
- if (fs4.existsSync(path4.join(moduleDir, "knowledge", "tech", "module", "module-context.md"))) {
17527
+ if (fs5.existsSync(path4.join(moduleDir, "knowledge", "tech", "module", "module-context.md"))) {
17482
17528
  return "knowledge/tech/module/module-context.md";
17483
17529
  }
17484
17530
  return "knowledge/module/module-context.md";
@@ -17867,27 +17913,27 @@ function modulesDiscoverTool(root, args) {
17867
17913
  const discovered = [];
17868
17914
  for (const wr of workspaceRoots2) {
17869
17915
  const rootAbs = path4.resolve(root, wr);
17870
- if (!fs4.existsSync(rootAbs) || !fs4.statSync(rootAbs).isDirectory()) continue;
17916
+ if (!fs5.existsSync(rootAbs) || !fs5.statSync(rootAbs).isDirectory()) continue;
17871
17917
  let entries;
17872
17918
  try {
17873
- entries = fs4.readdirSync(rootAbs);
17919
+ entries = fs5.readdirSync(rootAbs);
17874
17920
  } catch {
17875
17921
  continue;
17876
17922
  }
17877
17923
  for (const entry of entries) {
17878
17924
  if (IGNORED.has(entry)) continue;
17879
17925
  const candidateAbs = path4.resolve(rootAbs, entry);
17880
- if (!fs4.statSync(candidateAbs).isDirectory()) continue;
17926
+ if (!fs5.statSync(candidateAbs).isDirectory()) continue;
17881
17927
  if (candidateAbs === coreAbs) continue;
17882
17928
  const relPath = path4.relative(root, candidateAbs).replace(/\\/g, "/");
17883
17929
  const cfgPath = path4.join(candidateAbs, ".kaddo", "config.yml");
17884
- if (!fs4.existsSync(cfgPath)) {
17930
+ if (!fs5.existsSync(cfgPath)) {
17885
17931
  discovered.push({ id: entry, path: relPath, status: "not_configured", eligibleForMapping: false });
17886
17932
  continue;
17887
17933
  }
17888
17934
  let parsed = null;
17889
17935
  try {
17890
- parsed = parseYaml11(fs4.readFileSync(cfgPath, "utf-8"));
17936
+ parsed = parseYaml11(fs5.readFileSync(cfgPath, "utf-8"));
17891
17937
  } catch {
17892
17938
  }
17893
17939
  const role = parsed?.project?.role ?? parsed?.multirepo?.role;
@@ -17961,7 +18007,7 @@ ${list3(data.owners)}
17961
18007
 
17962
18008
  // ../cli/src/core/system-topology.ts
17963
18009
  import { parse as parseYaml12, stringify as stringifyYaml7 } from "yaml";
17964
- import fs5 from "fs";
18010
+ import fs6 from "fs";
17965
18011
  import path5 from "path";
17966
18012
  import crypto from "crypto";
17967
18013
  var TOPOLOGY_FILE2 = "knowledge/tech/system-topology.yml";
@@ -18478,7 +18524,7 @@ import matter14 from "gray-matter";
18478
18524
  import { parse as parseYaml13, stringify as stringifyYaml8 } from "yaml";
18479
18525
 
18480
18526
  // ../cli/src/core/work-item-write.ts
18481
- import fs6 from "fs";
18527
+ import fs7 from "fs";
18482
18528
  import path6 from "path";
18483
18529
  import crypto4 from "crypto";
18484
18530
  import matter13 from "gray-matter";
@@ -20974,7 +21020,8 @@ function createServer(root) {
20974
21020
  inputSchema: {
20975
21021
  content: z4.string().describe("The Work Item content \u2014 plain text, Markdown, or Kaddo-format Markdown with frontmatter"),
20976
21022
  type: z4.string().optional().describe("Kaddo Work Item type (feature/bugfix/hotfix/spike/chore). Defaults to feature if not detectable."),
20977
- source: z4.string().optional().describe("Import origin: chat (default), cli, admin")
21023
+ source: z4.string().optional().describe("Import origin: chat (default), cli, admin"),
21024
+ onConflict: z4.string().optional().describe('ID conflict resolution: "replace" deletes the existing WI and creates the new one; "new-id" assigns a new consecutive ID. Required when the imported content has an id that already exists.')
20978
21025
  }
20979
21026
  },
20980
21027
  async (args) => toolText(guarded(root, () => importWorkItemTool(root, args)))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/mcp",
3
- "version": "3.90.2",
3
+ "version": "3.91.0",
4
4
  "description": "MCP server for Kaddo project knowledge: read-only resources/tools/prompts plus safe derived generation under .kaddo/.",
5
5
  "license": "MIT",
6
6
  "repository": {