@kaddo/mcp 3.96.0 → 3.97.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 +242 -4
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -10285,6 +10285,18 @@ var RESOURCES = [
10285
10285
  return parts;
10286
10286
  }
10287
10287
  },
10288
+ {
10289
+ uri: "kaddo://build-contract",
10290
+ name: "Kaddo build contract",
10291
+ description: "The Kaddo-native Build Contract \u2014 lifecycle stages, entry/exit criteria, agents, skills, and CLI commands for the full Work Item lifecycle (Captured Intent \u2192 Completed).",
10292
+ mimeType: "text/markdown",
10293
+ read: (root) => fileOrHint(
10294
+ root,
10295
+ "kaddo://build-contract",
10296
+ "knowledge/delivery/build-contract.md",
10297
+ "Build Contract not found. Create knowledge/delivery/build-contract.md first."
10298
+ )
10299
+ },
10288
10300
  {
10289
10301
  uri: "kaddo://work-items",
10290
10302
  name: "Kaddo work items",
@@ -10533,8 +10545,11 @@ var EXTRA_INPUTS = {
10533
10545
  "graph-agent": [".kaddo/graph.json", ".kaddo/graph-hints.md"],
10534
10546
  "ownership-agent": ["knowledge/delivery/work-items/", "knowledge/inventory.md"],
10535
10547
  "capsule-agent": ["knowledge/product/capabilities.md", "knowledge/tech/decisions/"],
10536
- "work-item-agent": ["knowledge/delivery/roadmap.md"],
10537
- "implementation-agent": ["knowledge/tech/git-strategy.md"]
10548
+ "work-item-agent": ["knowledge/delivery/roadmap.md", "knowledge/delivery/build-contract.md"],
10549
+ "implementation-agent": ["knowledge/tech/git-strategy.md", "knowledge/delivery/build-contract.md", "knowledge/skills/implementation-planning/skill.md"],
10550
+ "backlog-agent": ["knowledge/delivery/roadmap.md", "knowledge/delivery/work-items/"],
10551
+ "architecture-agent": ["knowledge/tech/stack.md", "knowledge/tech/current-state.md", "knowledge/tech/decisions/"],
10552
+ "module-context-agent": ["knowledge/tech/codebase.md", "knowledge/tech/current-state.md"]
10538
10553
  };
10539
10554
  function recommendedInputs(name) {
10540
10555
  return [".kaddo/context-pack.md", ...EXTRA_INPUTS[name] ?? []];
@@ -12426,6 +12441,93 @@ function agentGroupOf2(fileName) {
12426
12441
  function agentInstallPath2(fileName) {
12427
12442
  return `knowledge/agents/${agentGroupOf2(fileName)}/${fileName}`;
12428
12443
  }
12444
+ var CANDIDATES_DISCOVERY2 = "knowledge/tech/discovery/decision-candidates.md";
12445
+ var CANDIDATES_LEGACY2 = "knowledge/tech/decision-candidates.md";
12446
+ var DECISIONS_DIR2 = "knowledge/tech/decisions";
12447
+ function resolveCandidatesPath2(dir) {
12448
+ const discovery = exists2(join3(dir, CANDIDATES_DISCOVERY2));
12449
+ const legacy = exists2(join3(dir, CANDIDATES_LEGACY2));
12450
+ if (discovery) return { path: CANDIDATES_DISCOVERY2, legacy: false, bothExist: legacy };
12451
+ if (legacy) return { path: CANDIDATES_LEGACY2, legacy: true, bothExist: false };
12452
+ return { path: null, legacy: false, bothExist: false };
12453
+ }
12454
+ function cleanCandidateTitle2(title) {
12455
+ return title.replace(/^\s*#{1,6}\s+/, "").replace(/^\s*[-*]\s+/, "").replace(/^\s*\(?\d+\)?[.):]\s+/, "").trim();
12456
+ }
12457
+ function slugify2(s) {
12458
+ return cleanCandidateTitle2(s).toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").slice(0, 70).replace(/-+$/g, "");
12459
+ }
12460
+ function parseDecisionCandidates2(md) {
12461
+ const out = [];
12462
+ for (const line of md.split(/\r?\n/)) {
12463
+ const m2 = line.match(/^##\s+(.+?)\s*$/);
12464
+ if (m2) {
12465
+ const raw = m2[1].trim();
12466
+ if (!raw || /^_.*_$/.test(raw)) continue;
12467
+ const t = cleanCandidateTitle2(raw);
12468
+ if (t) out.push(t);
12469
+ }
12470
+ }
12471
+ return out;
12472
+ }
12473
+ function countAdrs2(dir) {
12474
+ const base = join3(dir, DECISIONS_DIR2);
12475
+ if (!exists2(base)) return { total: 0, draft: 0, accepted: 0 };
12476
+ let total = 0;
12477
+ let draft = 0;
12478
+ let accepted = 0;
12479
+ for (const entry of readDir2(base)) {
12480
+ if (!entry.endsWith(".md") || entry === ".gitkeep") continue;
12481
+ const full = join3(base, entry);
12482
+ if (!isFile2(full)) continue;
12483
+ total += 1;
12484
+ let content = "";
12485
+ try {
12486
+ content = readFile2(full);
12487
+ } catch {
12488
+ continue;
12489
+ }
12490
+ const status = content.match(/^\s*status:\s*([a-z-]+)/im)?.[1]?.toLowerCase();
12491
+ if (status === "accepted") accepted += 1;
12492
+ else draft += 1;
12493
+ }
12494
+ return { total, draft, accepted };
12495
+ }
12496
+ function buildTechDecisions2(dir) {
12497
+ const resolved = resolveCandidatesPath2(dir);
12498
+ let titles = [];
12499
+ if (resolved.path) {
12500
+ try {
12501
+ titles = parseDecisionCandidates2(readFile2(join3(dir, resolved.path)));
12502
+ } catch {
12503
+ titles = [];
12504
+ }
12505
+ }
12506
+ const { total, draft, accepted } = countAdrs2(dir);
12507
+ const candidate_list = titles.map((title, i) => {
12508
+ const n = String(total + i + 1).padStart(3, "0");
12509
+ return { title, source: resolved.path, suggestedAdrFile: `${DECISIONS_DIR2}/ADR-${n}-${slugify2(title)}.md` };
12510
+ });
12511
+ let status;
12512
+ if (accepted > 0) status = "accepted-adrs";
12513
+ else if (total > 0) status = "draft-adrs";
12514
+ else if (titles.length > 0) status = "candidates";
12515
+ else status = "none";
12516
+ return {
12517
+ status,
12518
+ candidates: titles.length,
12519
+ adrs: total,
12520
+ draft_adrs: draft,
12521
+ accepted_adrs: accepted,
12522
+ candidate_list,
12523
+ candidates_source: resolved.path,
12524
+ candidates_legacy_location: resolved.legacy,
12525
+ candidates_both_exist: resolved.bothExist
12526
+ };
12527
+ }
12528
+ function hasUnmaterializedDecisions(td) {
12529
+ return td.candidates > 0 && td.adrs === 0;
12530
+ }
12429
12531
  function resolveVersion2() {
12430
12532
  try {
12431
12533
  let dir = dirname2(fileURLToPath2(import.meta.url));
@@ -16360,7 +16462,7 @@ var WorkItemWriteError = class extends Error {
16360
16462
  function revisionOf(raw) {
16361
16463
  return crypto2.createHash("sha256").update(raw, "utf-8").digest("hex");
16362
16464
  }
16363
- function slugify2(s) {
16465
+ function slugify22(s) {
16364
16466
  return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 50);
16365
16467
  }
16366
16468
  function nextWorkItemId(dir) {
@@ -16506,7 +16608,7 @@ function createWorkItem(dir, opts) {
16506
16608
  relatedKnowledge: [],
16507
16609
  scopeConfidence: null
16508
16610
  });
16509
- const relPath = `${WORK_ITEMS_DIR}/draft/${id}-${slugify2(title)}.md`;
16611
+ const relPath = `${WORK_ITEMS_DIR}/draft/${id}-${slugify22(title)}.md`;
16510
16612
  const filePath = join3(dir, relPath);
16511
16613
  if (exists2(filePath)) throw new WorkItemWriteError("INVALID_INPUT", `Work Item file already exists: ${relPath}`);
16512
16614
  const raw = serialize(data, body);
@@ -16762,6 +16864,121 @@ function buildRefinementHandoff(dir, workItemId) {
16762
16864
  text: lines.join("\n")
16763
16865
  };
16764
16866
  }
16867
+ var WorkItemNotReadyError = class extends Error {
16868
+ constructor(workItemId, actualState) {
16869
+ super(`Work Item "${workItemId}" must be in "ready" state to produce an Implementation Handoff, but is "${actualState}".`);
16870
+ this.workItemId = workItemId;
16871
+ this.actualState = actualState;
16872
+ this.name = "WorkItemNotReadyError";
16873
+ }
16874
+ workItemId;
16875
+ actualState;
16876
+ };
16877
+ var RECOMMENDED_AGENT2 = "implementation-agent";
16878
+ var RECOMMENDED_SKILL2 = "implementation-planning";
16879
+ function buildImplementationHandoff(dir, workItemId) {
16880
+ const artifacts = discoverWorkItems2(dir);
16881
+ const match = artifacts.find((a2) => (a2.id || a2.title) === workItemId);
16882
+ if (!match) throw new WorkItemNotFoundError(workItemId);
16883
+ const lifecycle = lifecycleStateOf2({ status: match.status, filePath: match.filePath });
16884
+ if (lifecycle !== "ready") throw new WorkItemNotReadyError(workItemId, lifecycle);
16885
+ const config = loadConfig2(dir);
16886
+ const projectName = config?.project.name ?? "this project";
16887
+ const mappedModules = loadMappedModules2(dir).map((m2) => m2.id);
16888
+ const multirepo = mappedModules.length > 0;
16889
+ const techDecisions = buildTechDecisions2(dir);
16890
+ const unmaterialized = hasUnmaterializedDecisions(techDecisions);
16891
+ const wiId = match.id || match.title;
16892
+ const wiTitle = match.title || match.id;
16893
+ const affectedModules = match.affectedModules;
16894
+ const domains = match.domains;
16895
+ const scopeConfidence = match.scopeConfidence;
16896
+ const lines = [
16897
+ `Implement Work Item ${wiId} \u2014 "${wiTitle}" \u2014 in project "${projectName}" using Kaddo.`,
16898
+ "",
16899
+ `Use the canonical ${RECOMMENDED_AGENT2} and the ${RECOMMENDED_SKILL2} skill (via Kaddo MCP or skills),`,
16900
+ "with access to this repository.",
16901
+ "",
16902
+ "--- Context Assembly ---",
16903
+ "",
16904
+ "Before planning, assemble context proportionally from these sources:",
16905
+ "",
16906
+ "1. Work Item: outcome, acceptance criteria, constraints, out-of-scope, affected modules, domains.",
16907
+ "2. Knowledge: business, product, tech and delivery knowledge relevant to the Work Item domains.",
16908
+ "3. System Context: topology, module dependencies, system graph when available.",
16909
+ "4. Repository Context: existing patterns, file structure, test conventions in affected modules.",
16910
+ "5. Related Work Items: dependencies (depends_on), completed related Work Items for precedent.",
16911
+ "",
16912
+ "Assemble proportionally: a single-module bugfix needs the affected module and its tests.",
16913
+ "A cross-cutting feature needs topology, multiple modules, and business context.",
16914
+ "",
16915
+ "--- Design Deliberation ---",
16916
+ "",
16917
+ "Before writing code, document:",
16918
+ "",
16919
+ "- Technical Approach: how this will be implemented \u2014 patterns, modules, layers involved.",
16920
+ "- Rationale: why this approach \u2014 reference existing patterns, constraints, or WI requirements.",
16921
+ "- Alternatives Considered: when the change is non-trivial, document alternative approaches.",
16922
+ "- Trade-offs: when alternatives exist, state what is gained and lost with the chosen approach.",
16923
+ "",
16924
+ "For trivial or obvious changes, Alternatives and Trade-offs may be omitted with a note explaining why."
16925
+ ];
16926
+ if (unmaterialized) {
16927
+ lines.push(
16928
+ "",
16929
+ "--- ADR Recommendation ---",
16930
+ "",
16931
+ "This project has unmaterialized decision candidates. When the implementation involves an",
16932
+ "architecturally significant decision, use the adr-writing skill to formalize it as an ADR",
16933
+ "before implementing. Check knowledge/tech/discovery/decision-candidates.md for candidates."
16934
+ );
16935
+ }
16936
+ if (multirepo) {
16937
+ lines.push(
16938
+ "",
16939
+ `This is a multirepo project. Evaluate the scope across all relevant mapped modules (${mappedModules.join(", ")})`,
16940
+ "before finalizing the implementation plan."
16941
+ );
16942
+ }
16943
+ const topology = getSystemMapProjection(dir).metadata.topologyStatus;
16944
+ if (topology !== "unavailable") {
16945
+ lines.push(
16946
+ "",
16947
+ `The semantic system Graph is ${topology}. When semantic system topology is available:`,
16948
+ "1. identify the relevant system entry points;",
16949
+ "2. query the Kaddo Graph (search / neighbors / paths) for connected entities;",
16950
+ "3. treat Graph results as impact candidates, not confirmed scope;",
16951
+ "4. inspect the actual implementation in the repository for each candidate."
16952
+ );
16953
+ }
16954
+ lines.push(
16955
+ "",
16956
+ "--- Implementation Plan ---",
16957
+ "",
16958
+ "Produce a plan with:",
16959
+ "- Context Summary: what you assembled and consulted.",
16960
+ "- Design Deliberation: Technical Approach, Rationale, and when relevant, Alternatives and Trade-offs.",
16961
+ "- Affected Areas: modules, files, surfaces impacted.",
16962
+ "- Implementation Steps: numbered steps with expected files per step.",
16963
+ "- Validation Plan: how to verify \u2014 test commands, manual steps, AC verification.",
16964
+ "- Stop Criteria: when to pause and ask \u2014 scope expansion, contradictions, missing knowledge.",
16965
+ "",
16966
+ "Do not start coding without explicit human confirmation of this plan."
16967
+ );
16968
+ return {
16969
+ workItemId: wiId,
16970
+ title: wiTitle,
16971
+ projectName,
16972
+ lifecycle,
16973
+ affectedModules,
16974
+ domains,
16975
+ scopeConfidence,
16976
+ hasUnmaterializedDecisions: unmaterialized,
16977
+ recommendedAgent: RECOMMENDED_AGENT2,
16978
+ recommendedSkill: RECOMMENDED_SKILL2,
16979
+ text: lines.join("\n")
16980
+ };
16981
+ }
16765
16982
  var WorkItemNotInProgressError = class extends Error {
16766
16983
  constructor(workItemId, actualState) {
16767
16984
  super(`Work Item "${workItemId}" must be in "in-progress" state for verification, but is "${actualState}".`);
@@ -18014,6 +18231,16 @@ function importWorkItemTool(root, args) {
18014
18231
  return fail("Work Item import failed.");
18015
18232
  }
18016
18233
  }
18234
+ function implementationHandoffTool(root, args) {
18235
+ try {
18236
+ const handoff = buildImplementationHandoff(root, args.workItemId);
18237
+ return ok(handoff);
18238
+ } catch (err) {
18239
+ if (err instanceof WorkItemNotFoundError) return fail(err.message);
18240
+ if (err instanceof WorkItemNotReadyError) return fail(err.message);
18241
+ return fail("Implementation handoff failed.");
18242
+ }
18243
+ }
18017
18244
  function collectEvidenceTool(root, args) {
18018
18245
  try {
18019
18246
  const result = collectImplementationEvidence(root, args.workItemId, args.evidence);
@@ -21596,6 +21823,17 @@ function createServer(root) {
21596
21823
  },
21597
21824
  async (args) => toolText(guarded(root, () => importWorkItemTool(root, args)))
21598
21825
  );
21826
+ server.registerTool(
21827
+ "kaddo_implementation_handoff",
21828
+ {
21829
+ title: "Build Implementation Handoff",
21830
+ description: 'Build an agent-agnostic Implementation Handoff for a ready Work Item. Assembles context guidance, design deliberation prompts, and implementation instructions. Returns structured handoff data including recommended agent, skill, affected modules, and copyable text. The Work Item must be in "ready" state. Does not start implementation \u2014 human confirmation is required first.',
21831
+ inputSchema: {
21832
+ workItemId: z4.string().describe("The Work Item ID (e.g. WI-005)")
21833
+ }
21834
+ },
21835
+ async (args) => toolText(guarded(root, () => implementationHandoffTool(root, args)))
21836
+ );
21599
21837
  const evidenceRepoSchema = z4.object({
21600
21838
  repoId: z4.string(),
21601
21839
  role: z4.string().optional(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/mcp",
3
- "version": "3.96.0",
3
+ "version": "3.97.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": {