@kaddo/mcp 3.57.0 → 3.58.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 +167 -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,7 @@ 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,
|
|
2422
2453
|
ready_work_items: byState("ready"),
|
|
2423
2454
|
in_progress_work_items: byState("in-progress"),
|
|
2424
2455
|
blocked_work_items: byState("blocked"),
|
|
@@ -2546,6 +2577,17 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
|
|
|
2546
2577
|
const secondary = buildSecondaryRecommendations(st);
|
|
2547
2578
|
const roadmap = roadmapSignal(dir);
|
|
2548
2579
|
if (roadmap !== "has-candidates" && st.draft_work_items > 0) {
|
|
2580
|
+
if (st.refined_draft_work_items > 0) {
|
|
2581
|
+
return {
|
|
2582
|
+
id: "review-work-item",
|
|
2583
|
+
phase: "Active Delivery",
|
|
2584
|
+
label: "Review the refined draft Work Item and mark it ready with `kaddo ready`.",
|
|
2585
|
+
command: "kaddo ready",
|
|
2586
|
+
mcpAction: "kaddo_mark_work_item_ready",
|
|
2587
|
+
reason: `There ${st.refined_draft_work_items === 1 ? "is" : "are"} ${st.refined_draft_work_items} refined draft Work Item${st.refined_draft_work_items === 1 ? "" : "s"} awaiting human review.`,
|
|
2588
|
+
...secondary.length ? { secondary } : {}
|
|
2589
|
+
};
|
|
2590
|
+
}
|
|
2549
2591
|
return {
|
|
2550
2592
|
id: "refine-work-item",
|
|
2551
2593
|
phase: "Active Delivery",
|
|
@@ -2912,6 +2954,8 @@ validation (how to test it), definition of done, open questions and dependencies
|
|
|
2912
2954
|
- Do not expand scope without explicit confirmation.
|
|
2913
2955
|
- Do not create mega Work Items \u2014 split when it covers multiple outcomes.
|
|
2914
2956
|
- Keep acceptance criteria testable.
|
|
2957
|
+
- This skill refines scope and acceptance criteria but does not approve implementation readiness.
|
|
2958
|
+
Readiness requires human confirmation through \`kaddo ready\` or the MCP \`mark_work_item_ready\` action.
|
|
2915
2959
|
|
|
2916
2960
|
## Quality checklist
|
|
2917
2961
|
|
|
@@ -3301,9 +3345,9 @@ var RESPONSIBILITY_MATRIX = {
|
|
|
3301
3345
|
agent: "work-item-agent",
|
|
3302
3346
|
responsibleFor: ["Work Item refinement"],
|
|
3303
3347
|
produces: ["knowledge/delivery/work-items/"],
|
|
3304
|
-
canSuggest: ["implementation-agent"],
|
|
3348
|
+
canSuggest: ["kaddo ready", "mark_work_item_ready", "implementation-agent"],
|
|
3305
3349
|
cannotSuggest: ["branches", "commits", "pull requests", "code"],
|
|
3306
|
-
next: ["implementation-agent"]
|
|
3350
|
+
next: ["kaddo ready (human review)", "implementation-agent"]
|
|
3307
3351
|
},
|
|
3308
3352
|
"implementation-agent": {
|
|
3309
3353
|
agent: "implementation-agent",
|
|
@@ -4316,7 +4360,16 @@ and \`blocked\` as active work; \`completed\` and \`archived\` are historical kn
|
|
|
4316
4360
|
|
|
4317
4361
|
## Handoff
|
|
4318
4362
|
|
|
4319
|
-
|
|
4363
|
+
After refining a Work Item, **do not mark it ready automatically**. If the Work Item appears
|
|
4364
|
+
complete (acceptance criteria, validation, code globs, domains all present, open questions
|
|
4365
|
+
resolved), recommend human review and the ready transition:
|
|
4366
|
+
|
|
4367
|
+
- **CLI:** \`kaddo ready <WI-ID>\`
|
|
4368
|
+
- **MCP:** \`mark_work_item_ready({ id: "<WI-ID>" })\`
|
|
4369
|
+
|
|
4370
|
+
The transition to \`ready\` requires explicit human confirmation. Only after a human marks the
|
|
4371
|
+
Work Item as ready should it be handed off to the implementation-agent.
|
|
4372
|
+
|
|
4320
4373
|
You do **not** suggest branches, commits or pull requests \u2014 implementation (including any Git
|
|
4321
4374
|
branch suggestion) is the implementation-agent's responsibility, and only by respecting the
|
|
4322
4375
|
project Git strategy. Your job ends at a clear, traceable Work Item that **states how to test it**.
|
|
@@ -6052,7 +6105,8 @@ function mapNextStepId(id) {
|
|
|
6052
6105
|
"refine-product": "define-product",
|
|
6053
6106
|
"refine-capabilities": "discover-capabilities",
|
|
6054
6107
|
"refine-current-state": "describe-architecture",
|
|
6055
|
-
"refine-codebase": "describe-architecture"
|
|
6108
|
+
"refine-codebase": "describe-architecture",
|
|
6109
|
+
"review-work-item": "refine-work-item"
|
|
6056
6110
|
};
|
|
6057
6111
|
return MAP[id] ?? id;
|
|
6058
6112
|
}
|
|
@@ -7680,7 +7734,14 @@ function optStr2(v) {
|
|
|
7680
7734
|
return typeof v === "string" && v.trim() ? v.trim() : void 0;
|
|
7681
7735
|
}
|
|
7682
7736
|
function parseSource(data) {
|
|
7683
|
-
const
|
|
7737
|
+
const rawSource = data.source;
|
|
7738
|
+
if (rawSource != null && typeof rawSource === "object" && !Array.isArray(rawSource)) {
|
|
7739
|
+
const obj = rawSource;
|
|
7740
|
+
const t = optStr2(obj.type);
|
|
7741
|
+
const type = t && VALID_SOURCES2.has(t) ? t : "unknown";
|
|
7742
|
+
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" };
|
|
7743
|
+
}
|
|
7744
|
+
const raw = rawSource ? String(rawSource) : "";
|
|
7684
7745
|
if (raw && VALID_SOURCES2.has(raw)) {
|
|
7685
7746
|
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
7747
|
}
|
|
@@ -7721,23 +7782,53 @@ function listWorkItems(root) {
|
|
|
7721
7782
|
}
|
|
7722
7783
|
const data = parsed.data;
|
|
7723
7784
|
if (!data.type) continue;
|
|
7785
|
+
const status = lifecycleOf(data, rel);
|
|
7786
|
+
const code = strArray(data.code);
|
|
7787
|
+
const domains = strArray(data.domains);
|
|
7788
|
+
const source = parseSource(data);
|
|
7789
|
+
const readiness = status === "draft" ? assessReadiness(data, parsed.content, code, domains, source) : void 0;
|
|
7724
7790
|
items.push({
|
|
7725
7791
|
id: String(data.id ?? "") || rel,
|
|
7726
7792
|
title: String(data.title ?? ""),
|
|
7727
|
-
status
|
|
7793
|
+
status,
|
|
7728
7794
|
type: String(data.type ?? ""),
|
|
7729
7795
|
knowledge_level: String(data.knowledge_level ?? ""),
|
|
7730
7796
|
path: rel,
|
|
7731
7797
|
summary: String(data.summary ?? "") || firstParagraph2(parsed.content),
|
|
7732
|
-
code
|
|
7798
|
+
code,
|
|
7733
7799
|
capabilities: strArray(data.capabilities),
|
|
7734
7800
|
decisions: strArray(data.decisions),
|
|
7735
7801
|
capsules: strArray(data.capsules),
|
|
7736
|
-
source
|
|
7802
|
+
source,
|
|
7803
|
+
domains,
|
|
7804
|
+
readiness
|
|
7737
7805
|
});
|
|
7738
7806
|
}
|
|
7739
7807
|
return items.sort((a, b) => a.id.localeCompare(b.id));
|
|
7740
7808
|
}
|
|
7809
|
+
function assessReadiness(data, content, code, domains, source) {
|
|
7810
|
+
const body = content.trim();
|
|
7811
|
+
const lower = body.toLowerCase();
|
|
7812
|
+
const warnings = [];
|
|
7813
|
+
if (domains.length === 0) warnings.push("No domains declared.");
|
|
7814
|
+
if (code.length === 0) warnings.push("No code ownership globs declared.");
|
|
7815
|
+
if (!/## acceptance criteria|## criterios de aceptación/i.test(body)) warnings.push("No acceptance criteria section found.");
|
|
7816
|
+
if (!/## validation|## validación/i.test(body)) warnings.push("No validation section found.");
|
|
7817
|
+
const oqMatch = body.match(/## (?:open questions|preguntas abiertas)\s*\n([\s\S]*?)(?=\n##|\n*$)/i);
|
|
7818
|
+
if (oqMatch) {
|
|
7819
|
+
const oqText = oqMatch[1].trim().toLowerCase();
|
|
7820
|
+
if (oqText && !/^(none|ninguna|resolved|resueltas|deferred|diferidas)\.?$/i.test(oqText)) {
|
|
7821
|
+
warnings.push("Open questions are still present.");
|
|
7822
|
+
}
|
|
7823
|
+
}
|
|
7824
|
+
const hasRefinedBy = Boolean(data.refined_by);
|
|
7825
|
+
const readyCandidate = hasRefinedBy && warnings.length === 0;
|
|
7826
|
+
return {
|
|
7827
|
+
readyCandidate,
|
|
7828
|
+
warnings,
|
|
7829
|
+
recommendedAction: readyCandidate ? "mark_work_item_ready" : void 0
|
|
7830
|
+
};
|
|
7831
|
+
}
|
|
7741
7832
|
|
|
7742
7833
|
// src/resources.ts
|
|
7743
7834
|
function text(uri, body, mimeType = "text/markdown") {
|
|
@@ -8092,6 +8183,7 @@ function getPrompt(root, name) {
|
|
|
8092
8183
|
}
|
|
8093
8184
|
|
|
8094
8185
|
// src/tools.ts
|
|
8186
|
+
import matter10 from "gray-matter";
|
|
8095
8187
|
var ok = (data) => ({ ok: true, data });
|
|
8096
8188
|
var fail = (message) => ({ ok: false, message });
|
|
8097
8189
|
function projectStatus(root) {
|
|
@@ -8137,6 +8229,62 @@ function getWorkItem(root, id) {
|
|
|
8137
8229
|
const content = readText(root, item.path);
|
|
8138
8230
|
return ok({ ...item, content: content ?? "" });
|
|
8139
8231
|
}
|
|
8232
|
+
function markWorkItemReady(root, id, confirm) {
|
|
8233
|
+
const item = listWorkItems(root).find((w) => w.id === id);
|
|
8234
|
+
if (!item) return fail(`Work Item "${id}" not found.`);
|
|
8235
|
+
if (item.status === "ready") {
|
|
8236
|
+
return ok({ status: "already_ready", id: item.id, message: `Work Item ${item.id} is already ready.` });
|
|
8237
|
+
}
|
|
8238
|
+
if (item.status !== "draft") {
|
|
8239
|
+
return fail(`Work Item ${item.id} is in state "${item.status}", not "draft". Only draft Work Items can be marked as ready.`);
|
|
8240
|
+
}
|
|
8241
|
+
const warnings = item.readiness?.warnings ?? [];
|
|
8242
|
+
if (!confirm) {
|
|
8243
|
+
return ok({
|
|
8244
|
+
status: "needs_confirmation",
|
|
8245
|
+
workItem: {
|
|
8246
|
+
id: item.id,
|
|
8247
|
+
title: item.title,
|
|
8248
|
+
type: item.type,
|
|
8249
|
+
status: item.status,
|
|
8250
|
+
domains: item.domains,
|
|
8251
|
+
source: item.source,
|
|
8252
|
+
code: item.code,
|
|
8253
|
+
readiness: item.readiness
|
|
8254
|
+
},
|
|
8255
|
+
command: `kaddo ready ${item.id}`,
|
|
8256
|
+
message: `Review this Work Item and call mark_work_item_ready with confirm=true to mark it ready.`
|
|
8257
|
+
});
|
|
8258
|
+
}
|
|
8259
|
+
const raw = readText(root, item.path);
|
|
8260
|
+
if (!raw) return fail(`Could not read Work Item file: ${item.path}`);
|
|
8261
|
+
const parsed = matter10(raw);
|
|
8262
|
+
const data = { ...parsed.data };
|
|
8263
|
+
const today2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
8264
|
+
data.status = "ready";
|
|
8265
|
+
data.ready_at = today2;
|
|
8266
|
+
const newContent = matter10.stringify(parsed.content, data);
|
|
8267
|
+
const posixPath = item.path.replace(/\\/g, "/");
|
|
8268
|
+
let newRelPath = item.path;
|
|
8269
|
+
if (posixPath.includes("/work-items/draft/")) {
|
|
8270
|
+
newRelPath = item.path.replace(/[/\\]draft[/\\]/, "/ready/");
|
|
8271
|
+
}
|
|
8272
|
+
writeWorkItemTransition(root, newRelPath, newContent);
|
|
8273
|
+
if (newRelPath !== item.path) {
|
|
8274
|
+
removeWorkItemFile(root, item.path);
|
|
8275
|
+
}
|
|
8276
|
+
const status = warnings.length > 0 ? "ready_with_warnings" : "ready";
|
|
8277
|
+
return ok({
|
|
8278
|
+
status,
|
|
8279
|
+
id: item.id,
|
|
8280
|
+
from: "draft",
|
|
8281
|
+
to: "ready",
|
|
8282
|
+
ready_at: today2,
|
|
8283
|
+
moved: newRelPath !== item.path ? { from: item.path, to: newRelPath } : void 0,
|
|
8284
|
+
...warnings.length > 0 ? { warnings } : {},
|
|
8285
|
+
next: ["Run kaddo context", "Run kaddo understand"]
|
|
8286
|
+
});
|
|
8287
|
+
}
|
|
8140
8288
|
function listCapsulesTool(root) {
|
|
8141
8289
|
return ok(listCapsules(root));
|
|
8142
8290
|
}
|
|
@@ -8178,7 +8326,7 @@ function listGraphHints(root, filter = {}) {
|
|
|
8178
8326
|
}
|
|
8179
8327
|
|
|
8180
8328
|
// ../cli/src/core/context-pack.ts
|
|
8181
|
-
import
|
|
8329
|
+
import matter11 from "gray-matter";
|
|
8182
8330
|
var CONTEXT_PACK_VERSION = "1";
|
|
8183
8331
|
var ARCH_DIR3 = "knowledge";
|
|
8184
8332
|
function readScanJson(dir) {
|
|
@@ -8191,7 +8339,7 @@ function readScanJson(dir) {
|
|
|
8191
8339
|
}
|
|
8192
8340
|
}
|
|
8193
8341
|
function firstParagraph3(markdown) {
|
|
8194
|
-
const body =
|
|
8342
|
+
const body = matter11(markdown).content.trim();
|
|
8195
8343
|
const para = body.split("\n\n").map((p) => p.trim()).find((p) => p && !p.startsWith("#"));
|
|
8196
8344
|
return para ?? "";
|
|
8197
8345
|
}
|
|
@@ -9253,6 +9401,15 @@ function createServer(root) {
|
|
|
9253
9401
|
{ title: "Get Work Item", description: "Get a Work Item by ID (summary + full markdown).", inputSchema: { id: z2.string() } },
|
|
9254
9402
|
async (args) => toolText(guarded(root, () => getWorkItem(root, args.id)))
|
|
9255
9403
|
);
|
|
9404
|
+
server.registerTool(
|
|
9405
|
+
"kaddo_mark_work_item_ready",
|
|
9406
|
+
{
|
|
9407
|
+
title: "Mark Work Item ready",
|
|
9408
|
+
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.",
|
|
9409
|
+
inputSchema: { id: z2.string(), confirm: z2.boolean().optional() }
|
|
9410
|
+
},
|
|
9411
|
+
async (args) => toolText(guarded(root, () => markWorkItemReady(root, args.id, args.confirm)))
|
|
9412
|
+
);
|
|
9256
9413
|
server.registerTool(
|
|
9257
9414
|
"kaddo_list_capsules",
|
|
9258
9415
|
{ title: "List Knowledge Capsules", description: "List registered external Knowledge Capsules.", inputSchema: {} },
|
package/package.json
CHANGED