@kaddo/mcp 3.57.0 → 3.59.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.
- package/dist/index.js +171 -10
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -111,6 +111,36 @@ function writeDerived(root, relPath, content) {
|
|
|
111
111
|
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
112
112
|
fs.writeFileSync(abs, content, "utf-8");
|
|
113
113
|
}
|
|
114
|
+
function writeWorkItemTransition(root, relPath, content) {
|
|
115
|
+
const p = toPosix(relPath).replace(/^\/+/, "");
|
|
116
|
+
if (path.isAbsolute(relPath) || p.split("/").some((seg) => seg === "..")) {
|
|
117
|
+
throw new KaddoMcpError("Blocked unsafe work-item write path.");
|
|
118
|
+
}
|
|
119
|
+
if (!p.startsWith("knowledge/delivery/work-items/")) {
|
|
120
|
+
throw new KaddoMcpError("Work Item transitions can only write under knowledge/delivery/work-items/.");
|
|
121
|
+
}
|
|
122
|
+
if (!p.endsWith(".md")) {
|
|
123
|
+
throw new KaddoMcpError("Work Item files must be markdown.");
|
|
124
|
+
}
|
|
125
|
+
const abs = path.resolve(root, p);
|
|
126
|
+
const base = toPosix(root).replace(/\/+$/, "");
|
|
127
|
+
if (toPosix(abs) !== base && !toPosix(abs).startsWith(base + "/")) {
|
|
128
|
+
throw new KaddoMcpError("Blocked unsafe work-item write path.");
|
|
129
|
+
}
|
|
130
|
+
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
131
|
+
fs.writeFileSync(abs, content, "utf-8");
|
|
132
|
+
}
|
|
133
|
+
function removeWorkItemFile(root, relPath) {
|
|
134
|
+
const p = toPosix(relPath).replace(/^\/+/, "");
|
|
135
|
+
if (path.isAbsolute(relPath) || p.split("/").some((seg) => seg === "..")) {
|
|
136
|
+
throw new KaddoMcpError("Blocked unsafe work-item remove path.");
|
|
137
|
+
}
|
|
138
|
+
if (!p.startsWith("knowledge/delivery/work-items/")) {
|
|
139
|
+
throw new KaddoMcpError("Work Item removal can only target knowledge/delivery/work-items/.");
|
|
140
|
+
}
|
|
141
|
+
const abs = path.resolve(root, p);
|
|
142
|
+
if (fs.existsSync(abs)) fs.unlinkSync(abs);
|
|
143
|
+
}
|
|
114
144
|
function listFiles(root, relDir, filterExt) {
|
|
115
145
|
let absDir;
|
|
116
146
|
try {
|
|
@@ -2419,6 +2449,8 @@ function buildDeliveryState(dir) {
|
|
|
2419
2449
|
return {
|
|
2420
2450
|
phase: "",
|
|
2421
2451
|
draft_work_items: byState("draft"),
|
|
2452
|
+
refined_draft_work_items: wis.filter((w) => w.lifecycle === "draft" && w.rawFrontmatter.refined_by).length,
|
|
2453
|
+
refined_draft_ids: wis.filter((w) => w.lifecycle === "draft" && w.rawFrontmatter.refined_by).map((w) => w.id),
|
|
2422
2454
|
ready_work_items: byState("ready"),
|
|
2423
2455
|
in_progress_work_items: byState("in-progress"),
|
|
2424
2456
|
blocked_work_items: byState("blocked"),
|
|
@@ -2546,6 +2578,20 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
|
|
|
2546
2578
|
const secondary = buildSecondaryRecommendations(st);
|
|
2547
2579
|
const roadmap = roadmapSignal(dir);
|
|
2548
2580
|
if (roadmap !== "has-candidates" && st.draft_work_items > 0) {
|
|
2581
|
+
if (st.refined_draft_work_items > 0) {
|
|
2582
|
+
const ids = st.refined_draft_ids;
|
|
2583
|
+
const single = ids.length === 1;
|
|
2584
|
+
return {
|
|
2585
|
+
id: "review-work-item",
|
|
2586
|
+
phase: "Active Delivery",
|
|
2587
|
+
label: single ? `Review ${ids[0]} and mark it ready for implementation.` : "Review refined draft Work Items and mark one ready for implementation.",
|
|
2588
|
+
command: single ? `kaddo ready ${ids[0]}` : "kaddo ready <WI-ID>",
|
|
2589
|
+
mcpAction: "kaddo_mark_work_item_ready",
|
|
2590
|
+
...single ? { target: ids[0], mcpArgs: { id: ids[0] } } : { targets: ids },
|
|
2591
|
+
reason: single ? `${ids[0]} is a refined draft Work Item awaiting human review.` : `There are ${ids.length} refined draft Work Items awaiting human review.`,
|
|
2592
|
+
...secondary.length ? { secondary } : {}
|
|
2593
|
+
};
|
|
2594
|
+
}
|
|
2549
2595
|
return {
|
|
2550
2596
|
id: "refine-work-item",
|
|
2551
2597
|
phase: "Active Delivery",
|
|
@@ -2912,6 +2958,8 @@ validation (how to test it), definition of done, open questions and dependencies
|
|
|
2912
2958
|
- Do not expand scope without explicit confirmation.
|
|
2913
2959
|
- Do not create mega Work Items \u2014 split when it covers multiple outcomes.
|
|
2914
2960
|
- Keep acceptance criteria testable.
|
|
2961
|
+
- This skill refines scope and acceptance criteria but does not approve implementation readiness.
|
|
2962
|
+
Readiness requires human confirmation through \`kaddo ready\` or the MCP \`mark_work_item_ready\` action.
|
|
2915
2963
|
|
|
2916
2964
|
## Quality checklist
|
|
2917
2965
|
|
|
@@ -3301,9 +3349,9 @@ var RESPONSIBILITY_MATRIX = {
|
|
|
3301
3349
|
agent: "work-item-agent",
|
|
3302
3350
|
responsibleFor: ["Work Item refinement"],
|
|
3303
3351
|
produces: ["knowledge/delivery/work-items/"],
|
|
3304
|
-
canSuggest: ["implementation-agent"],
|
|
3352
|
+
canSuggest: ["kaddo ready", "mark_work_item_ready", "implementation-agent"],
|
|
3305
3353
|
cannotSuggest: ["branches", "commits", "pull requests", "code"],
|
|
3306
|
-
next: ["implementation-agent"]
|
|
3354
|
+
next: ["kaddo ready (human review)", "implementation-agent"]
|
|
3307
3355
|
},
|
|
3308
3356
|
"implementation-agent": {
|
|
3309
3357
|
agent: "implementation-agent",
|
|
@@ -4316,7 +4364,16 @@ and \`blocked\` as active work; \`completed\` and \`archived\` are historical kn
|
|
|
4316
4364
|
|
|
4317
4365
|
## Handoff
|
|
4318
4366
|
|
|
4319
|
-
|
|
4367
|
+
After refining a Work Item, **do not mark it ready automatically**. If the Work Item appears
|
|
4368
|
+
complete (acceptance criteria, validation, code globs, domains all present, open questions
|
|
4369
|
+
resolved), recommend human review and the ready transition:
|
|
4370
|
+
|
|
4371
|
+
- **CLI:** \`kaddo ready <WI-ID>\`
|
|
4372
|
+
- **MCP:** \`mark_work_item_ready({ id: "<WI-ID>" })\`
|
|
4373
|
+
|
|
4374
|
+
The transition to \`ready\` requires explicit human confirmation. Only after a human marks the
|
|
4375
|
+
Work Item as ready should it be handed off to the implementation-agent.
|
|
4376
|
+
|
|
4320
4377
|
You do **not** suggest branches, commits or pull requests \u2014 implementation (including any Git
|
|
4321
4378
|
branch suggestion) is the implementation-agent's responsibility, and only by respecting the
|
|
4322
4379
|
project Git strategy. Your job ends at a clear, traceable Work Item that **states how to test it**.
|
|
@@ -6052,7 +6109,8 @@ function mapNextStepId(id) {
|
|
|
6052
6109
|
"refine-product": "define-product",
|
|
6053
6110
|
"refine-capabilities": "discover-capabilities",
|
|
6054
6111
|
"refine-current-state": "describe-architecture",
|
|
6055
|
-
"refine-codebase": "describe-architecture"
|
|
6112
|
+
"refine-codebase": "describe-architecture",
|
|
6113
|
+
"review-work-item": "refine-work-item"
|
|
6056
6114
|
};
|
|
6057
6115
|
return MAP[id] ?? id;
|
|
6058
6116
|
}
|
|
@@ -7680,7 +7738,14 @@ function optStr2(v) {
|
|
|
7680
7738
|
return typeof v === "string" && v.trim() ? v.trim() : void 0;
|
|
7681
7739
|
}
|
|
7682
7740
|
function parseSource(data) {
|
|
7683
|
-
const
|
|
7741
|
+
const rawSource = data.source;
|
|
7742
|
+
if (rawSource != null && typeof rawSource === "object" && !Array.isArray(rawSource)) {
|
|
7743
|
+
const obj = rawSource;
|
|
7744
|
+
const t = optStr2(obj.type);
|
|
7745
|
+
const type = t && VALID_SOURCES2.has(t) ? t : "unknown";
|
|
7746
|
+
return { type, id: optStr2(obj.id) ?? optStr2(data.source_id), title: optStr2(obj.title) ?? optStr2(data.source_title), url: optStr2(obj.url) ?? optStr2(data.source_url), provider: optStr2(obj.provider) ?? optStr2(data.source_provider), inferred: obj.inferred === true || obj.inferred === "true" };
|
|
7747
|
+
}
|
|
7748
|
+
const raw = rawSource ? String(rawSource) : "";
|
|
7684
7749
|
if (raw && VALID_SOURCES2.has(raw)) {
|
|
7685
7750
|
return { type: raw, id: optStr2(data.source_id), title: optStr2(data.source_title), url: optStr2(data.source_url), provider: optStr2(data.source_provider), inferred: false };
|
|
7686
7751
|
}
|
|
@@ -7721,23 +7786,53 @@ function listWorkItems(root) {
|
|
|
7721
7786
|
}
|
|
7722
7787
|
const data = parsed.data;
|
|
7723
7788
|
if (!data.type) continue;
|
|
7789
|
+
const status = lifecycleOf(data, rel);
|
|
7790
|
+
const code = strArray(data.code);
|
|
7791
|
+
const domains = strArray(data.domains);
|
|
7792
|
+
const source = parseSource(data);
|
|
7793
|
+
const readiness = status === "draft" ? assessReadiness(data, parsed.content, code, domains, source) : void 0;
|
|
7724
7794
|
items.push({
|
|
7725
7795
|
id: String(data.id ?? "") || rel,
|
|
7726
7796
|
title: String(data.title ?? ""),
|
|
7727
|
-
status
|
|
7797
|
+
status,
|
|
7728
7798
|
type: String(data.type ?? ""),
|
|
7729
7799
|
knowledge_level: String(data.knowledge_level ?? ""),
|
|
7730
7800
|
path: rel,
|
|
7731
7801
|
summary: String(data.summary ?? "") || firstParagraph2(parsed.content),
|
|
7732
|
-
code
|
|
7802
|
+
code,
|
|
7733
7803
|
capabilities: strArray(data.capabilities),
|
|
7734
7804
|
decisions: strArray(data.decisions),
|
|
7735
7805
|
capsules: strArray(data.capsules),
|
|
7736
|
-
source
|
|
7806
|
+
source,
|
|
7807
|
+
domains,
|
|
7808
|
+
readiness
|
|
7737
7809
|
});
|
|
7738
7810
|
}
|
|
7739
7811
|
return items.sort((a, b) => a.id.localeCompare(b.id));
|
|
7740
7812
|
}
|
|
7813
|
+
function assessReadiness(data, content, code, domains, source) {
|
|
7814
|
+
const body = content.trim();
|
|
7815
|
+
const lower = body.toLowerCase();
|
|
7816
|
+
const warnings = [];
|
|
7817
|
+
if (domains.length === 0) warnings.push("No domains declared.");
|
|
7818
|
+
if (code.length === 0) warnings.push("No code ownership globs declared.");
|
|
7819
|
+
if (!/## acceptance criteria|## criterios de aceptación/i.test(body)) warnings.push("No acceptance criteria section found.");
|
|
7820
|
+
if (!/## validation|## validación/i.test(body)) warnings.push("No validation section found.");
|
|
7821
|
+
const oqMatch = body.match(/## (?:open questions|preguntas abiertas)\s*\n([\s\S]*?)(?=\n##|\n*$)/i);
|
|
7822
|
+
if (oqMatch) {
|
|
7823
|
+
const oqText = oqMatch[1].trim().toLowerCase();
|
|
7824
|
+
if (oqText && !/^(none|ninguna|resolved|resueltas|deferred|diferidas)\.?$/i.test(oqText)) {
|
|
7825
|
+
warnings.push("Open questions are still present.");
|
|
7826
|
+
}
|
|
7827
|
+
}
|
|
7828
|
+
const hasRefinedBy = Boolean(data.refined_by);
|
|
7829
|
+
const readyCandidate = hasRefinedBy && warnings.length === 0;
|
|
7830
|
+
return {
|
|
7831
|
+
readyCandidate,
|
|
7832
|
+
warnings,
|
|
7833
|
+
recommendedAction: readyCandidate ? "mark_work_item_ready" : void 0
|
|
7834
|
+
};
|
|
7835
|
+
}
|
|
7741
7836
|
|
|
7742
7837
|
// src/resources.ts
|
|
7743
7838
|
function text(uri, body, mimeType = "text/markdown") {
|
|
@@ -8092,6 +8187,7 @@ function getPrompt(root, name) {
|
|
|
8092
8187
|
}
|
|
8093
8188
|
|
|
8094
8189
|
// src/tools.ts
|
|
8190
|
+
import matter10 from "gray-matter";
|
|
8095
8191
|
var ok = (data) => ({ ok: true, data });
|
|
8096
8192
|
var fail = (message) => ({ ok: false, message });
|
|
8097
8193
|
function projectStatus(root) {
|
|
@@ -8137,6 +8233,62 @@ function getWorkItem(root, id) {
|
|
|
8137
8233
|
const content = readText(root, item.path);
|
|
8138
8234
|
return ok({ ...item, content: content ?? "" });
|
|
8139
8235
|
}
|
|
8236
|
+
function markWorkItemReady(root, id, confirm) {
|
|
8237
|
+
const item = listWorkItems(root).find((w) => w.id === id);
|
|
8238
|
+
if (!item) return fail(`Work Item "${id}" not found.`);
|
|
8239
|
+
if (item.status === "ready") {
|
|
8240
|
+
return ok({ status: "already_ready", id: item.id, message: `Work Item ${item.id} is already ready.` });
|
|
8241
|
+
}
|
|
8242
|
+
if (item.status !== "draft") {
|
|
8243
|
+
return fail(`Work Item ${item.id} is in state "${item.status}", not "draft". Only draft Work Items can be marked as ready.`);
|
|
8244
|
+
}
|
|
8245
|
+
const warnings = item.readiness?.warnings ?? [];
|
|
8246
|
+
if (!confirm) {
|
|
8247
|
+
return ok({
|
|
8248
|
+
status: "needs_confirmation",
|
|
8249
|
+
workItem: {
|
|
8250
|
+
id: item.id,
|
|
8251
|
+
title: item.title,
|
|
8252
|
+
type: item.type,
|
|
8253
|
+
status: item.status,
|
|
8254
|
+
domains: item.domains,
|
|
8255
|
+
source: item.source,
|
|
8256
|
+
code: item.code,
|
|
8257
|
+
readiness: item.readiness
|
|
8258
|
+
},
|
|
8259
|
+
command: `kaddo ready ${item.id}`,
|
|
8260
|
+
message: `Review this Work Item and call mark_work_item_ready with confirm=true to mark it ready.`
|
|
8261
|
+
});
|
|
8262
|
+
}
|
|
8263
|
+
const raw = readText(root, item.path);
|
|
8264
|
+
if (!raw) return fail(`Could not read Work Item file: ${item.path}`);
|
|
8265
|
+
const parsed = matter10(raw);
|
|
8266
|
+
const data = { ...parsed.data };
|
|
8267
|
+
const today2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
8268
|
+
data.status = "ready";
|
|
8269
|
+
data.ready_at = today2;
|
|
8270
|
+
const newContent = matter10.stringify(parsed.content, data);
|
|
8271
|
+
const posixPath = item.path.replace(/\\/g, "/");
|
|
8272
|
+
let newRelPath = item.path;
|
|
8273
|
+
if (posixPath.includes("/work-items/draft/")) {
|
|
8274
|
+
newRelPath = item.path.replace(/[/\\]draft[/\\]/, "/ready/");
|
|
8275
|
+
}
|
|
8276
|
+
writeWorkItemTransition(root, newRelPath, newContent);
|
|
8277
|
+
if (newRelPath !== item.path) {
|
|
8278
|
+
removeWorkItemFile(root, item.path);
|
|
8279
|
+
}
|
|
8280
|
+
const status = warnings.length > 0 ? "ready_with_warnings" : "ready";
|
|
8281
|
+
return ok({
|
|
8282
|
+
status,
|
|
8283
|
+
id: item.id,
|
|
8284
|
+
from: "draft",
|
|
8285
|
+
to: "ready",
|
|
8286
|
+
ready_at: today2,
|
|
8287
|
+
moved: newRelPath !== item.path ? { from: item.path, to: newRelPath } : void 0,
|
|
8288
|
+
...warnings.length > 0 ? { warnings } : {},
|
|
8289
|
+
next: ["Run kaddo context", "Run kaddo understand"]
|
|
8290
|
+
});
|
|
8291
|
+
}
|
|
8140
8292
|
function listCapsulesTool(root) {
|
|
8141
8293
|
return ok(listCapsules(root));
|
|
8142
8294
|
}
|
|
@@ -8178,7 +8330,7 @@ function listGraphHints(root, filter = {}) {
|
|
|
8178
8330
|
}
|
|
8179
8331
|
|
|
8180
8332
|
// ../cli/src/core/context-pack.ts
|
|
8181
|
-
import
|
|
8333
|
+
import matter11 from "gray-matter";
|
|
8182
8334
|
var CONTEXT_PACK_VERSION = "1";
|
|
8183
8335
|
var ARCH_DIR3 = "knowledge";
|
|
8184
8336
|
function readScanJson(dir) {
|
|
@@ -8191,7 +8343,7 @@ function readScanJson(dir) {
|
|
|
8191
8343
|
}
|
|
8192
8344
|
}
|
|
8193
8345
|
function firstParagraph3(markdown) {
|
|
8194
|
-
const body =
|
|
8346
|
+
const body = matter11(markdown).content.trim();
|
|
8195
8347
|
const para = body.split("\n\n").map((p) => p.trim()).find((p) => p && !p.startsWith("#"));
|
|
8196
8348
|
return para ?? "";
|
|
8197
8349
|
}
|
|
@@ -9253,6 +9405,15 @@ function createServer(root) {
|
|
|
9253
9405
|
{ title: "Get Work Item", description: "Get a Work Item by ID (summary + full markdown).", inputSchema: { id: z2.string() } },
|
|
9254
9406
|
async (args) => toolText(guarded(root, () => getWorkItem(root, args.id)))
|
|
9255
9407
|
);
|
|
9408
|
+
server.registerTool(
|
|
9409
|
+
"kaddo_mark_work_item_ready",
|
|
9410
|
+
{
|
|
9411
|
+
title: "Mark Work Item ready",
|
|
9412
|
+
description: "Assess whether a draft Work Item is ready for implementation. Without confirm, returns a preview with readiness warnings. With confirm=true, transitions the Work Item from draft to ready.",
|
|
9413
|
+
inputSchema: { id: z2.string(), confirm: z2.boolean().optional() }
|
|
9414
|
+
},
|
|
9415
|
+
async (args) => toolText(guarded(root, () => markWorkItemReady(root, args.id, args.confirm)))
|
|
9416
|
+
);
|
|
9256
9417
|
server.registerTool(
|
|
9257
9418
|
"kaddo_list_capsules",
|
|
9258
9419
|
{ title: "List Knowledge Capsules", description: "List registered external Knowledge Capsules.", inputSchema: {} },
|
package/package.json
CHANGED