@kaddo/mcp 3.43.0 → 3.45.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 +440 -139
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1629,7 +1629,12 @@ function splitInitiatives(markdown) {
|
|
|
1629
1629
|
return blocks;
|
|
1630
1630
|
}
|
|
1631
1631
|
function parseBlock(block) {
|
|
1632
|
-
const meta = {
|
|
1632
|
+
const meta = {
|
|
1633
|
+
relatedCapabilities: [],
|
|
1634
|
+
dependencies: [],
|
|
1635
|
+
openQuestions: [],
|
|
1636
|
+
sourceSignals: []
|
|
1637
|
+
};
|
|
1633
1638
|
const rawCandidates = [];
|
|
1634
1639
|
let listField = null;
|
|
1635
1640
|
let inCandidateSection = false;
|
|
@@ -1679,7 +1684,7 @@ function parseBlock(block) {
|
|
|
1679
1684
|
meta.impact = value || void 0;
|
|
1680
1685
|
} else if (key === "risk") {
|
|
1681
1686
|
meta.risk = value || void 0;
|
|
1682
|
-
} else if (key === "project area / domain" || key === "domain" || key === "project area") {
|
|
1687
|
+
} else if (key === "project area / domain" || key === "domain" || key === "project area" || key === "related domain") {
|
|
1683
1688
|
meta.domain = value || void 0;
|
|
1684
1689
|
} else if (key === "related capabilities") {
|
|
1685
1690
|
listField = "relatedCapabilities";
|
|
@@ -1690,6 +1695,9 @@ function parseBlock(block) {
|
|
|
1690
1695
|
} else if (key === "open questions") {
|
|
1691
1696
|
listField = "openQuestions";
|
|
1692
1697
|
if (value) meta.openQuestions.push(value);
|
|
1698
|
+
} else if (key === "source signals" || key === "source signal") {
|
|
1699
|
+
listField = "sourceSignals";
|
|
1700
|
+
if (value) meta.sourceSignals.push(value);
|
|
1693
1701
|
}
|
|
1694
1702
|
continue;
|
|
1695
1703
|
}
|
|
@@ -1707,6 +1715,7 @@ function parseBlock(block) {
|
|
|
1707
1715
|
}
|
|
1708
1716
|
}
|
|
1709
1717
|
flush();
|
|
1718
|
+
const decisionCandidates = decisionCandidatesFromSignals(meta.sourceSignals);
|
|
1710
1719
|
return rawCandidates.map((c) => ({
|
|
1711
1720
|
...c,
|
|
1712
1721
|
initiative: block.initiative.id || block.initiative.title ? { ...block.initiative } : void 0,
|
|
@@ -1715,9 +1724,23 @@ function parseBlock(block) {
|
|
|
1715
1724
|
impact: meta.impact,
|
|
1716
1725
|
risk: meta.risk,
|
|
1717
1726
|
dependencies: meta.dependencies.length ? [...meta.dependencies] : void 0,
|
|
1718
|
-
openQuestions: meta.openQuestions.length ? [...meta.openQuestions] : void 0
|
|
1727
|
+
openQuestions: meta.openQuestions.length ? [...meta.openQuestions] : void 0,
|
|
1728
|
+
sourceSignals: meta.sourceSignals.length ? [...meta.sourceSignals] : void 0,
|
|
1729
|
+
decisionCandidates: decisionCandidates.length ? decisionCandidates : void 0
|
|
1719
1730
|
}));
|
|
1720
1731
|
}
|
|
1732
|
+
function decisionCandidatesFromSignals(signals) {
|
|
1733
|
+
const out = [];
|
|
1734
|
+
for (const sig of signals) {
|
|
1735
|
+
const m = sig.match(/decision candidate\s*:?\s*(.+)$/i);
|
|
1736
|
+
if (!m) continue;
|
|
1737
|
+
const rest = m[1].trim();
|
|
1738
|
+
const token = rest.match(/\b[A-Z][A-Z0-9_]{2,}\b/);
|
|
1739
|
+
const value = token ? token[0] : rest.replace(/[.。]+$/, "").trim();
|
|
1740
|
+
if (value && !out.includes(value)) out.push(value);
|
|
1741
|
+
}
|
|
1742
|
+
return out;
|
|
1743
|
+
}
|
|
1721
1744
|
var WI_ID_RE = /\bWI-[A-Za-z0-9-]*\d/;
|
|
1722
1745
|
var HEADING_RE = /^#{2,4}\s+(.*)$/;
|
|
1723
1746
|
var FLEX_BULLET_RE = /^\s*[-*]\s+(?:\[[ xX]?\]\s+)?(WI-[A-Za-z0-9-]*\d)\b[\s:.\-–)]*\s*(.*)$/;
|
|
@@ -1799,10 +1822,36 @@ function parseRoadmapCandidates(markdown) {
|
|
|
1799
1822
|
if (strict.length > 0) return strict;
|
|
1800
1823
|
return parseFlexible(markdown);
|
|
1801
1824
|
}
|
|
1825
|
+
function countRoadmapInitiatives(markdown) {
|
|
1826
|
+
if (markdown == null) return 0;
|
|
1827
|
+
let n = 0;
|
|
1828
|
+
for (const line of markdown.split(/\r?\n/)) if (INITIATIVE_RE.test(line)) n++;
|
|
1829
|
+
return n;
|
|
1830
|
+
}
|
|
1802
1831
|
function roadmapStats(markdown, materialized) {
|
|
1803
|
-
if (markdown == null)
|
|
1832
|
+
if (markdown == null)
|
|
1833
|
+
return {
|
|
1834
|
+
present: false,
|
|
1835
|
+
initiatives: 0,
|
|
1836
|
+
work_item_candidates: 0,
|
|
1837
|
+
materialized_work_items: materialized,
|
|
1838
|
+
remaining_work_item_candidates: 0,
|
|
1839
|
+
candidates: 0,
|
|
1840
|
+
materialized,
|
|
1841
|
+
remaining: 0
|
|
1842
|
+
};
|
|
1804
1843
|
const candidates = parseRoadmapCandidates(markdown).length;
|
|
1805
|
-
|
|
1844
|
+
const remaining = Math.max(0, candidates - materialized);
|
|
1845
|
+
return {
|
|
1846
|
+
present: true,
|
|
1847
|
+
initiatives: countRoadmapInitiatives(markdown),
|
|
1848
|
+
work_item_candidates: candidates,
|
|
1849
|
+
materialized_work_items: materialized,
|
|
1850
|
+
remaining_work_item_candidates: remaining,
|
|
1851
|
+
candidates,
|
|
1852
|
+
materialized,
|
|
1853
|
+
remaining
|
|
1854
|
+
};
|
|
1806
1855
|
}
|
|
1807
1856
|
|
|
1808
1857
|
// ../cli/src/core/open-questions.ts
|
|
@@ -2233,7 +2282,124 @@ function buildAdapterStatuses(dir) {
|
|
|
2233
2282
|
});
|
|
2234
2283
|
}
|
|
2235
2284
|
|
|
2285
|
+
// ../cli/src/core/decisions.ts
|
|
2286
|
+
var CANDIDATES_DISCOVERY = "knowledge/tech/discovery/decision-candidates.md";
|
|
2287
|
+
var CANDIDATES_LEGACY = "knowledge/tech/decision-candidates.md";
|
|
2288
|
+
var DECISIONS_DIR = "knowledge/tech/decisions";
|
|
2289
|
+
function resolveCandidatesPath(dir) {
|
|
2290
|
+
const discovery = exists(join(dir, CANDIDATES_DISCOVERY));
|
|
2291
|
+
const legacy = exists(join(dir, CANDIDATES_LEGACY));
|
|
2292
|
+
if (discovery) return { path: CANDIDATES_DISCOVERY, legacy: false, bothExist: legacy };
|
|
2293
|
+
if (legacy) return { path: CANDIDATES_LEGACY, legacy: true, bothExist: false };
|
|
2294
|
+
return { path: null, legacy: false, bothExist: false };
|
|
2295
|
+
}
|
|
2296
|
+
function cleanCandidateTitle(title) {
|
|
2297
|
+
return title.replace(/^\s*#{1,6}\s+/, "").replace(/^\s*[-*]\s+/, "").replace(/^\s*\(?\d+\)?[.):]\s+/, "").trim();
|
|
2298
|
+
}
|
|
2299
|
+
function slugify(s) {
|
|
2300
|
+
return cleanCandidateTitle(s).toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").slice(0, 70).replace(/-+$/g, "");
|
|
2301
|
+
}
|
|
2302
|
+
function parseDecisionCandidates(md) {
|
|
2303
|
+
const out = [];
|
|
2304
|
+
for (const line of md.split(/\r?\n/)) {
|
|
2305
|
+
const m = line.match(/^##\s+(.+?)\s*$/);
|
|
2306
|
+
if (m) {
|
|
2307
|
+
const raw = m[1].trim();
|
|
2308
|
+
if (!raw || /^_.*_$/.test(raw)) continue;
|
|
2309
|
+
const t = cleanCandidateTitle(raw);
|
|
2310
|
+
if (t) out.push(t);
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
return out;
|
|
2314
|
+
}
|
|
2315
|
+
function countAdrs(dir) {
|
|
2316
|
+
const base = join(dir, DECISIONS_DIR);
|
|
2317
|
+
if (!exists(base)) return { total: 0, draft: 0, accepted: 0 };
|
|
2318
|
+
let total = 0;
|
|
2319
|
+
let draft = 0;
|
|
2320
|
+
let accepted = 0;
|
|
2321
|
+
for (const entry of readDir(base)) {
|
|
2322
|
+
if (!entry.endsWith(".md") || entry === ".gitkeep") continue;
|
|
2323
|
+
const full = join(base, entry);
|
|
2324
|
+
if (!isFile(full)) continue;
|
|
2325
|
+
total += 1;
|
|
2326
|
+
let content = "";
|
|
2327
|
+
try {
|
|
2328
|
+
content = readFile(full);
|
|
2329
|
+
} catch {
|
|
2330
|
+
continue;
|
|
2331
|
+
}
|
|
2332
|
+
const status = content.match(/^\s*status:\s*([a-z-]+)/im)?.[1]?.toLowerCase();
|
|
2333
|
+
if (status === "accepted") accepted += 1;
|
|
2334
|
+
else draft += 1;
|
|
2335
|
+
}
|
|
2336
|
+
return { total, draft, accepted };
|
|
2337
|
+
}
|
|
2338
|
+
function buildTechDecisions(dir) {
|
|
2339
|
+
const resolved = resolveCandidatesPath(dir);
|
|
2340
|
+
let titles = [];
|
|
2341
|
+
if (resolved.path) {
|
|
2342
|
+
try {
|
|
2343
|
+
titles = parseDecisionCandidates(readFile(join(dir, resolved.path)));
|
|
2344
|
+
} catch {
|
|
2345
|
+
titles = [];
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
const { total, draft, accepted } = countAdrs(dir);
|
|
2349
|
+
const candidate_list = titles.map((title, i) => {
|
|
2350
|
+
const n = String(total + i + 1).padStart(3, "0");
|
|
2351
|
+
return { title, source: resolved.path, suggestedAdrFile: `${DECISIONS_DIR}/ADR-${n}-${slugify(title)}.md` };
|
|
2352
|
+
});
|
|
2353
|
+
let status;
|
|
2354
|
+
if (accepted > 0) status = "accepted-adrs";
|
|
2355
|
+
else if (total > 0) status = "draft-adrs";
|
|
2356
|
+
else if (titles.length > 0) status = "candidates";
|
|
2357
|
+
else status = "none";
|
|
2358
|
+
return {
|
|
2359
|
+
status,
|
|
2360
|
+
candidates: titles.length,
|
|
2361
|
+
adrs: total,
|
|
2362
|
+
draft_adrs: draft,
|
|
2363
|
+
accepted_adrs: accepted,
|
|
2364
|
+
candidate_list,
|
|
2365
|
+
candidates_source: resolved.path,
|
|
2366
|
+
candidates_legacy_location: resolved.legacy,
|
|
2367
|
+
candidates_both_exist: resolved.bothExist
|
|
2368
|
+
};
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2236
2371
|
// ../cli/src/core/next-step.ts
|
|
2372
|
+
function buildDeliveryState(dir) {
|
|
2373
|
+
const wis = discoverWorkItems(dir);
|
|
2374
|
+
const byState = (s) => wis.filter((w) => w.lifecycle === s).length;
|
|
2375
|
+
const total = wis.length;
|
|
2376
|
+
const withOwnership = wis.filter((w) => w.codeGlobs.length > 0).length;
|
|
2377
|
+
const td = buildTechDecisions(dir);
|
|
2378
|
+
const roadmapPath = join(dir, "knowledge/delivery/roadmap.md");
|
|
2379
|
+
const roadmapMd = exists(roadmapPath) ? safeRead(roadmapPath) : null;
|
|
2380
|
+
const stats = roadmapStats(roadmapMd, total);
|
|
2381
|
+
const adapters = installedAdapters(dir);
|
|
2382
|
+
return {
|
|
2383
|
+
phase: "",
|
|
2384
|
+
draft_work_items: byState("draft"),
|
|
2385
|
+
ready_work_items: byState("ready"),
|
|
2386
|
+
in_progress_work_items: byState("in-progress"),
|
|
2387
|
+
blocked_work_items: byState("blocked"),
|
|
2388
|
+
total_work_items: total,
|
|
2389
|
+
ownership_coverage: `${withOwnership}/${total}`,
|
|
2390
|
+
remaining_work_item_candidates: stats.remaining_work_item_candidates,
|
|
2391
|
+
decision_candidates: td.candidates,
|
|
2392
|
+
accepted_adrs: td.accepted_adrs,
|
|
2393
|
+
adapters_installed: adapters.length
|
|
2394
|
+
};
|
|
2395
|
+
}
|
|
2396
|
+
function safeRead(p) {
|
|
2397
|
+
try {
|
|
2398
|
+
return readFile(p);
|
|
2399
|
+
} catch {
|
|
2400
|
+
return null;
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2237
2403
|
function roadmapSignal(dir) {
|
|
2238
2404
|
const p = join(dir, "knowledge/delivery/roadmap.md");
|
|
2239
2405
|
if (!exists(p)) return "missing";
|
|
@@ -2330,15 +2496,117 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
|
|
|
2330
2496
|
if (roadmap !== "has-candidates") {
|
|
2331
2497
|
return { id: "roadmap", phase: "Planning", label: "Use roadmap-agent to define roadmap candidates (`kaddo roadmap`).", command: "kaddo roadmap", agent: "roadmap-agent", target: "knowledge/delivery/roadmap.md", reason: "The roadmap has no candidates yet." };
|
|
2332
2498
|
}
|
|
2333
|
-
const
|
|
2334
|
-
|
|
2335
|
-
|
|
2499
|
+
const st = buildDeliveryState(dir);
|
|
2500
|
+
const secondary = buildSecondaryRecommendations(st);
|
|
2501
|
+
if (st.total_work_items === 0) {
|
|
2502
|
+
return {
|
|
2503
|
+
id: "create-work-item",
|
|
2504
|
+
phase: "Delivery Preparation",
|
|
2505
|
+
label: "Run `kaddo create --from roadmap` to materialize the first Work Item.",
|
|
2506
|
+
command: "kaddo create --from roadmap",
|
|
2507
|
+
reason: "The roadmap has candidates but no Work Item exists yet.",
|
|
2508
|
+
...secondary.length ? { secondary } : {}
|
|
2509
|
+
};
|
|
2336
2510
|
}
|
|
2337
|
-
const adapters =
|
|
2338
|
-
if (
|
|
2339
|
-
|
|
2511
|
+
const adapters = st.adapters_installed;
|
|
2512
|
+
if (st.ready_work_items > 0) {
|
|
2513
|
+
if (adapters === 0) {
|
|
2514
|
+
return {
|
|
2515
|
+
id: "install-adapter",
|
|
2516
|
+
phase: "Active Delivery",
|
|
2517
|
+
label: "Install or configure an adapter before implementation (`kaddo adapters list`).",
|
|
2518
|
+
command: "kaddo adapters list",
|
|
2519
|
+
reason: `${st.ready_work_items} Work Item(s) are ready but no adapter is installed.`,
|
|
2520
|
+
...secondary.length ? { secondary } : {}
|
|
2521
|
+
};
|
|
2522
|
+
}
|
|
2523
|
+
return {
|
|
2524
|
+
id: "implement",
|
|
2525
|
+
phase: "Active Delivery",
|
|
2526
|
+
label: "Use the implementation-agent or your installed adapter to plan implementation.",
|
|
2527
|
+
agent: "implementation-agent",
|
|
2528
|
+
reason: `${st.ready_work_items} Work Item(s) are ready and an adapter is installed.`,
|
|
2529
|
+
...secondary.length ? { secondary } : {}
|
|
2530
|
+
};
|
|
2531
|
+
}
|
|
2532
|
+
if (st.in_progress_work_items > 0) {
|
|
2533
|
+
return {
|
|
2534
|
+
id: "guard",
|
|
2535
|
+
phase: "Active Delivery",
|
|
2536
|
+
label: "Run `kaddo guard` and update affected knowledge after significant changes.",
|
|
2537
|
+
command: "kaddo guard",
|
|
2538
|
+
reason: `${st.in_progress_work_items} Work Item(s) are in progress.`,
|
|
2539
|
+
...secondary.length ? { secondary } : {}
|
|
2540
|
+
};
|
|
2541
|
+
}
|
|
2542
|
+
if (st.draft_work_items > 0) {
|
|
2543
|
+
return {
|
|
2544
|
+
id: "refine-work-item",
|
|
2545
|
+
phase: "Active Delivery",
|
|
2546
|
+
label: "Refine the existing draft Work Item with the work-item-agent.",
|
|
2547
|
+
agent: "work-item-agent",
|
|
2548
|
+
skill: "work-item-refinement",
|
|
2549
|
+
reason: `There ${st.draft_work_items === 1 ? "is" : "are"} ${st.draft_work_items} draft Work Item${st.draft_work_items === 1 ? "" : "s"} and no Work Item is ready.`,
|
|
2550
|
+
...secondary.length ? { secondary } : {}
|
|
2551
|
+
};
|
|
2552
|
+
}
|
|
2553
|
+
if (st.blocked_work_items > 0) {
|
|
2554
|
+
return {
|
|
2555
|
+
id: "resolve-blocker",
|
|
2556
|
+
phase: "Active Delivery",
|
|
2557
|
+
label: "Resolve the blocker on the blocked Work Item with the work-item-agent.",
|
|
2558
|
+
agent: "work-item-agent",
|
|
2559
|
+
reason: `${st.blocked_work_items} Work Item(s) are blocked.`,
|
|
2560
|
+
...secondary.length ? { secondary } : {}
|
|
2561
|
+
};
|
|
2562
|
+
}
|
|
2563
|
+
if (st.remaining_work_item_candidates > 0) {
|
|
2564
|
+
return {
|
|
2565
|
+
id: "materialize-more-work-items",
|
|
2566
|
+
phase: "Maintenance",
|
|
2567
|
+
label: `Materialize the remaining ${st.remaining_work_item_candidates} Work Item candidate(s) with \`kaddo create --from roadmap\`.`,
|
|
2568
|
+
command: "kaddo create --from roadmap",
|
|
2569
|
+
reason: "No active Work Items remain; roadmap candidates are still pending.",
|
|
2570
|
+
...secondary.length ? { secondary } : {}
|
|
2571
|
+
};
|
|
2572
|
+
}
|
|
2573
|
+
return {
|
|
2574
|
+
id: "plan-next",
|
|
2575
|
+
phase: "Maintenance",
|
|
2576
|
+
label: "Use the roadmap-agent to plan the next initiative.",
|
|
2577
|
+
agent: "roadmap-agent",
|
|
2578
|
+
reason: "No active Work Items and no remaining roadmap candidates."
|
|
2579
|
+
};
|
|
2580
|
+
}
|
|
2581
|
+
function buildSecondaryRecommendations(st) {
|
|
2582
|
+
const out = [];
|
|
2583
|
+
const [withOwnership] = st.ownership_coverage.split("/").map(Number);
|
|
2584
|
+
if (st.total_work_items > 0 && withOwnership < st.total_work_items) {
|
|
2585
|
+
out.push({
|
|
2586
|
+
id: "suggest-ownership",
|
|
2587
|
+
label: "Run `kaddo owners suggest` for Work Items without code ownership.",
|
|
2588
|
+
command: "kaddo owners suggest",
|
|
2589
|
+
reason: `Ownership coverage is ${st.ownership_coverage}.`
|
|
2590
|
+
});
|
|
2340
2591
|
}
|
|
2341
|
-
|
|
2592
|
+
if (st.total_work_items > 0 && st.decision_candidates > 0 && st.accepted_adrs === 0) {
|
|
2593
|
+
out.push({
|
|
2594
|
+
id: "materialize-adrs",
|
|
2595
|
+
label: "Use the adr-writing skill (`kaddo adr`) to materialize decision candidates into ADRs before implementing related technical Work Items.",
|
|
2596
|
+
command: "kaddo adr",
|
|
2597
|
+
skill: "adr-writing",
|
|
2598
|
+
reason: `There are ${st.decision_candidates} technical decision candidate(s) and ${st.accepted_adrs} accepted ADR(s).`
|
|
2599
|
+
});
|
|
2600
|
+
}
|
|
2601
|
+
if (st.total_work_items > 0 && st.remaining_work_item_candidates > 0) {
|
|
2602
|
+
out.push({
|
|
2603
|
+
id: "materialize-more-work-items",
|
|
2604
|
+
label: `Later, materialize the remaining ${st.remaining_work_item_candidates} Work Item candidate(s) with \`kaddo create --from roadmap\`.`,
|
|
2605
|
+
command: "kaddo create --from roadmap",
|
|
2606
|
+
reason: `There are ${st.remaining_work_item_candidates} remaining Work Item candidate(s).`
|
|
2607
|
+
});
|
|
2608
|
+
}
|
|
2609
|
+
return out;
|
|
2342
2610
|
}
|
|
2343
2611
|
|
|
2344
2612
|
// ../cli/src/core/readiness.ts
|
|
@@ -2433,92 +2701,6 @@ function buildReadinessReport(dir, now = /* @__PURE__ */ new Date()) {
|
|
|
2433
2701
|
};
|
|
2434
2702
|
}
|
|
2435
2703
|
|
|
2436
|
-
// ../cli/src/core/decisions.ts
|
|
2437
|
-
var CANDIDATES_DISCOVERY = "knowledge/tech/discovery/decision-candidates.md";
|
|
2438
|
-
var CANDIDATES_LEGACY = "knowledge/tech/decision-candidates.md";
|
|
2439
|
-
var DECISIONS_DIR = "knowledge/tech/decisions";
|
|
2440
|
-
function resolveCandidatesPath(dir) {
|
|
2441
|
-
const discovery = exists(join(dir, CANDIDATES_DISCOVERY));
|
|
2442
|
-
const legacy = exists(join(dir, CANDIDATES_LEGACY));
|
|
2443
|
-
if (discovery) return { path: CANDIDATES_DISCOVERY, legacy: false, bothExist: legacy };
|
|
2444
|
-
if (legacy) return { path: CANDIDATES_LEGACY, legacy: true, bothExist: false };
|
|
2445
|
-
return { path: null, legacy: false, bothExist: false };
|
|
2446
|
-
}
|
|
2447
|
-
function cleanCandidateTitle(title) {
|
|
2448
|
-
return title.replace(/^\s*#{1,6}\s+/, "").replace(/^\s*[-*]\s+/, "").replace(/^\s*\(?\d+\)?[.):]\s+/, "").trim();
|
|
2449
|
-
}
|
|
2450
|
-
function slugify(s) {
|
|
2451
|
-
return cleanCandidateTitle(s).toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").slice(0, 70).replace(/-+$/g, "");
|
|
2452
|
-
}
|
|
2453
|
-
function parseDecisionCandidates(md) {
|
|
2454
|
-
const out = [];
|
|
2455
|
-
for (const line of md.split(/\r?\n/)) {
|
|
2456
|
-
const m = line.match(/^##\s+(.+?)\s*$/);
|
|
2457
|
-
if (m) {
|
|
2458
|
-
const raw = m[1].trim();
|
|
2459
|
-
if (!raw || /^_.*_$/.test(raw)) continue;
|
|
2460
|
-
const t = cleanCandidateTitle(raw);
|
|
2461
|
-
if (t) out.push(t);
|
|
2462
|
-
}
|
|
2463
|
-
}
|
|
2464
|
-
return out;
|
|
2465
|
-
}
|
|
2466
|
-
function countAdrs(dir) {
|
|
2467
|
-
const base = join(dir, DECISIONS_DIR);
|
|
2468
|
-
if (!exists(base)) return { total: 0, draft: 0, accepted: 0 };
|
|
2469
|
-
let total = 0;
|
|
2470
|
-
let draft = 0;
|
|
2471
|
-
let accepted = 0;
|
|
2472
|
-
for (const entry of readDir(base)) {
|
|
2473
|
-
if (!entry.endsWith(".md") || entry === ".gitkeep") continue;
|
|
2474
|
-
const full = join(base, entry);
|
|
2475
|
-
if (!isFile(full)) continue;
|
|
2476
|
-
total += 1;
|
|
2477
|
-
let content = "";
|
|
2478
|
-
try {
|
|
2479
|
-
content = readFile(full);
|
|
2480
|
-
} catch {
|
|
2481
|
-
continue;
|
|
2482
|
-
}
|
|
2483
|
-
const status = content.match(/^\s*status:\s*([a-z-]+)/im)?.[1]?.toLowerCase();
|
|
2484
|
-
if (status === "accepted") accepted += 1;
|
|
2485
|
-
else draft += 1;
|
|
2486
|
-
}
|
|
2487
|
-
return { total, draft, accepted };
|
|
2488
|
-
}
|
|
2489
|
-
function buildTechDecisions(dir) {
|
|
2490
|
-
const resolved = resolveCandidatesPath(dir);
|
|
2491
|
-
let titles = [];
|
|
2492
|
-
if (resolved.path) {
|
|
2493
|
-
try {
|
|
2494
|
-
titles = parseDecisionCandidates(readFile(join(dir, resolved.path)));
|
|
2495
|
-
} catch {
|
|
2496
|
-
titles = [];
|
|
2497
|
-
}
|
|
2498
|
-
}
|
|
2499
|
-
const { total, draft, accepted } = countAdrs(dir);
|
|
2500
|
-
const candidate_list = titles.map((title, i) => {
|
|
2501
|
-
const n = String(total + i + 1).padStart(3, "0");
|
|
2502
|
-
return { title, source: resolved.path, suggestedAdrFile: `${DECISIONS_DIR}/ADR-${n}-${slugify(title)}.md` };
|
|
2503
|
-
});
|
|
2504
|
-
let status;
|
|
2505
|
-
if (accepted > 0) status = "accepted-adrs";
|
|
2506
|
-
else if (total > 0) status = "draft-adrs";
|
|
2507
|
-
else if (titles.length > 0) status = "candidates";
|
|
2508
|
-
else status = "none";
|
|
2509
|
-
return {
|
|
2510
|
-
status,
|
|
2511
|
-
candidates: titles.length,
|
|
2512
|
-
adrs: total,
|
|
2513
|
-
draft_adrs: draft,
|
|
2514
|
-
accepted_adrs: accepted,
|
|
2515
|
-
candidate_list,
|
|
2516
|
-
candidates_source: resolved.path,
|
|
2517
|
-
candidates_legacy_location: resolved.legacy,
|
|
2518
|
-
candidates_both_exist: resolved.bothExist
|
|
2519
|
-
};
|
|
2520
|
-
}
|
|
2521
|
-
|
|
2522
2704
|
// ../cli/src/core/assets.ts
|
|
2523
2705
|
import matter6 from "gray-matter";
|
|
2524
2706
|
|
|
@@ -4015,13 +4197,22 @@ that it should be materialized first (\`kaddo adr\` + the adr-writing skill) and
|
|
|
4015
4197
|
without surfacing it. -->
|
|
4016
4198
|
\`\`\`
|
|
4017
4199
|
|
|
4018
|
-
### Preserve roadmap metadata (VS-077)
|
|
4200
|
+
### Preserve roadmap metadata (VS-077 / VS-078)
|
|
4201
|
+
|
|
4202
|
+
When a Work Item comes from \`kaddo create --from roadmap\`, the front matter already carries the trace
|
|
4203
|
+
back to the roadmap. **Keep and refine \u2014 never delete** these fields:
|
|
4019
4204
|
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
\`
|
|
4205
|
+
- \`source_roadmap_initiative\` and \`source_work_item_candidate\` (the RM-xxx initiative and the
|
|
4206
|
+
WI-CANDIDATE-xxx it was materialized from)
|
|
4207
|
+
- \`related_domain\` and \`domains\` (keep them consistent \u2014 \`domains\` must not be empty when
|
|
4208
|
+
\`related_domain\` exists)
|
|
4209
|
+
- \`related_capabilities\` (a real list, one capability per item \u2014 never a single comma-joined string)
|
|
4210
|
+
- \`expected_value\`, \`risks\`, \`dependencies\`
|
|
4211
|
+
- \`source_signals\` (do **not** invent them \u2014 if absent, leave them absent)
|
|
4212
|
+
- \`decision_candidates\` and \`related_decisions\` (when the work depends on a technical decision)
|
|
4213
|
+
|
|
4214
|
+
Do not drop the trace back to the capability domain and source signals. If a Work Item depends on a
|
|
4215
|
+
tech decision candidate with no ADR yet, keep the warning surfaced in the body.
|
|
4025
4216
|
|
|
4026
4217
|
## Where to Save the Result
|
|
4027
4218
|
|
|
@@ -5304,16 +5495,25 @@ function buildRoadmapQuality(dir) {
|
|
|
5304
5495
|
const items = parseRoadmapCandidateQuality(md);
|
|
5305
5496
|
const count = (pred) => items.filter(pred).length;
|
|
5306
5497
|
const grounded = count((i) => i.grounded);
|
|
5307
|
-
|
|
5308
|
-
|
|
5498
|
+
const initiatives = {
|
|
5499
|
+
total: items.length,
|
|
5309
5500
|
grounded,
|
|
5310
5501
|
with_related_domain: count((i) => i.hasRelatedDomain),
|
|
5311
5502
|
with_related_capability: count((i) => i.hasRelatedCapability),
|
|
5312
5503
|
with_source_signals: count((i) => i.hasSourceSignals),
|
|
5313
|
-
// Only "needs refinement" when there are
|
|
5504
|
+
// Only "needs refinement" when there are initiatives and at least one isn't grounded.
|
|
5314
5505
|
needs_refinement: items.length > 0 && grounded < items.length,
|
|
5315
5506
|
items
|
|
5316
5507
|
};
|
|
5508
|
+
const wiCandidates = md ? parseRoadmapCandidates(md) : [];
|
|
5509
|
+
const wcount = (pred) => wiCandidates.filter(pred).length;
|
|
5510
|
+
const work_item_candidates = {
|
|
5511
|
+
total: wiCandidates.length,
|
|
5512
|
+
with_source_initiative: wcount((c) => Boolean(c.initiative?.id || c.initiative?.title)),
|
|
5513
|
+
with_related_domain: wcount((c) => Boolean(c.domain)),
|
|
5514
|
+
with_related_capability: wcount((c) => Boolean(c.relatedCapabilities?.length))
|
|
5515
|
+
};
|
|
5516
|
+
return { initiatives, work_item_candidates };
|
|
5317
5517
|
}
|
|
5318
5518
|
|
|
5319
5519
|
// ../cli/src/core/project-explain.ts
|
|
@@ -5596,10 +5796,11 @@ function renderExplanationHuman(exp) {
|
|
|
5596
5796
|
lines.push(`- Delivery: ${ls("Delivery")}`);
|
|
5597
5797
|
lines.push(`- Agents: ${exp.knowledge.hasAgents ? "available" : "missing"}`);
|
|
5598
5798
|
if (exp.roadmap.present) {
|
|
5599
|
-
lines.push(`- Roadmap
|
|
5600
|
-
lines.push(`-
|
|
5601
|
-
|
|
5602
|
-
|
|
5799
|
+
lines.push(`- Roadmap initiatives: ${exp.roadmap.initiatives}`);
|
|
5800
|
+
lines.push(`- Work Item candidates: ${exp.roadmap.work_item_candidates}`);
|
|
5801
|
+
lines.push(`- Materialized Work Items: ${exp.roadmap.materialized_work_items}`);
|
|
5802
|
+
if (exp.roadmap.remaining_work_item_candidates > 0)
|
|
5803
|
+
lines.push(`- Remaining Work Item candidates: ${exp.roadmap.remaining_work_item_candidates}`);
|
|
5603
5804
|
} else {
|
|
5604
5805
|
lines.push(`- Work items: ${exp.workItems.total}`);
|
|
5605
5806
|
}
|
|
@@ -5724,9 +5925,12 @@ function renderExplanationHuman(exp) {
|
|
|
5724
5925
|
}
|
|
5725
5926
|
lines.push(`- Next step: ${exp.readiness.recommended_next_step.label}`);
|
|
5726
5927
|
lines.push("");
|
|
5727
|
-
|
|
5928
|
+
const rec = exp.nextStepRecommendation;
|
|
5929
|
+
const secondary = rec.secondary ?? [];
|
|
5930
|
+
const steps = secondary.length > 0 || /Delivery|Active|Maintenance/.test(rec.phase) ? [rec.label, ...secondary.map((s2) => s2.label)] : exp.suggestedNextSteps;
|
|
5931
|
+
if (steps.length > 0) {
|
|
5728
5932
|
lines.push("## Suggested Next Steps");
|
|
5729
|
-
|
|
5933
|
+
steps.forEach((s2, i) => lines.push(`${i + 1}. ${s2}`));
|
|
5730
5934
|
lines.push("");
|
|
5731
5935
|
}
|
|
5732
5936
|
const r = exp.readiness;
|
|
@@ -5776,17 +5980,42 @@ function renderExplanationHuman(exp) {
|
|
|
5776
5980
|
lines.push("Tech discovery files are in the legacy `knowledge/tech/` root. Suggested cleanup: run `kaddo tech organize`.");
|
|
5777
5981
|
lines.push("");
|
|
5778
5982
|
}
|
|
5983
|
+
if (exp.roadmap.present) {
|
|
5984
|
+
lines.push("## Roadmap Status");
|
|
5985
|
+
lines.push(`- Initiatives: ${exp.roadmap.initiatives}`);
|
|
5986
|
+
lines.push(`- Work Item candidates: ${exp.roadmap.work_item_candidates}`);
|
|
5987
|
+
lines.push(`- Materialized Work Items: ${exp.roadmap.materialized_work_items}`);
|
|
5988
|
+
lines.push(`- Remaining Work Item candidates: ${exp.roadmap.remaining_work_item_candidates}`);
|
|
5989
|
+
lines.push("");
|
|
5990
|
+
}
|
|
5779
5991
|
const rq = exp.roadmapQuality;
|
|
5780
|
-
|
|
5992
|
+
const rqi = rq.initiatives;
|
|
5993
|
+
const rqw = rq.work_item_candidates;
|
|
5994
|
+
if (rqi.total > 0 || rqw.total > 0) {
|
|
5781
5995
|
lines.push("## Roadmap Quality");
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5996
|
+
if (rqi.total > 0) {
|
|
5997
|
+
lines.push("Initiatives:");
|
|
5998
|
+
lines.push(`- Candidates evaluated: ${rqi.total}`);
|
|
5999
|
+
lines.push(`- Grounded: ${rqi.grounded}/${rqi.total}`);
|
|
6000
|
+
lines.push(`- With related domain: ${rqi.with_related_domain}/${rqi.total}`);
|
|
6001
|
+
lines.push(`- With related capability: ${rqi.with_related_capability}/${rqi.total}`);
|
|
6002
|
+
lines.push(`- With source signals: ${rqi.with_source_signals}/${rqi.total}`);
|
|
6003
|
+
}
|
|
6004
|
+
if (rqw.total > 0) {
|
|
5788
6005
|
lines.push("");
|
|
5789
|
-
lines.push("
|
|
6006
|
+
lines.push("Work Item Candidates:");
|
|
6007
|
+
lines.push(`- Candidates: ${rqw.total}`);
|
|
6008
|
+
lines.push(`- With source initiative: ${rqw.with_source_initiative}/${rqw.total}`);
|
|
6009
|
+
lines.push(`- With related domain: ${rqw.with_related_domain}/${rqw.total}`);
|
|
6010
|
+
lines.push(`- With related capability: ${rqw.with_related_capability}/${rqw.total}`);
|
|
6011
|
+
}
|
|
6012
|
+
if (rqi.needs_refinement) {
|
|
6013
|
+
lines.push("");
|
|
6014
|
+
lines.push("Roadmap quality: needs refinement.");
|
|
6015
|
+
lines.push("Suggested: use roadmap-agent to add domain / capability / source signals.");
|
|
6016
|
+
} else if (rqw.total > 0) {
|
|
6017
|
+
lines.push("");
|
|
6018
|
+
lines.push("Work Item candidate quality: good.");
|
|
5790
6019
|
}
|
|
5791
6020
|
lines.push("");
|
|
5792
6021
|
}
|
|
@@ -7019,7 +7248,7 @@ var RESOURCES = [
|
|
|
7019
7248
|
{
|
|
7020
7249
|
uri: "kaddo://roadmap-quality",
|
|
7021
7250
|
name: "Kaddo roadmap quality",
|
|
7022
|
-
description: "How well roadmap candidates are grounded in capability domains, related capabilities and source signals (VS-077). Read-only.",
|
|
7251
|
+
description: "How well roadmap initiatives and Work Item candidates are grounded in capability domains, related capabilities and source signals (VS-077 / VS-077.1). Two levels: initiatives + work_item_candidates. Read-only.",
|
|
7023
7252
|
mimeType: "application/json",
|
|
7024
7253
|
read: (root) => {
|
|
7025
7254
|
if (!hasKnowledge(root)) {
|
|
@@ -7027,6 +7256,34 @@ var RESOURCES = [
|
|
|
7027
7256
|
}
|
|
7028
7257
|
return [{ uri: "kaddo://roadmap-quality", text: JSON.stringify(buildRoadmapQuality(root), null, 2), mimeType: "application/json" }];
|
|
7029
7258
|
}
|
|
7259
|
+
},
|
|
7260
|
+
{
|
|
7261
|
+
uri: "kaddo://work-item-candidates",
|
|
7262
|
+
name: "Kaddo work item candidates",
|
|
7263
|
+
description: "Materializable Work Item candidates (WI-CANDIDATE-xxx) parsed from the roadmap, with their source initiative and inherited metadata (VS-078). Read-only.",
|
|
7264
|
+
mimeType: "application/json",
|
|
7265
|
+
read: (root) => {
|
|
7266
|
+
if (!hasKnowledge(root)) {
|
|
7267
|
+
return text("kaddo://work-item-candidates", "Knowledge repository not found. Run `kaddo bootstrap` first.", "text/plain");
|
|
7268
|
+
}
|
|
7269
|
+
const md = readText(root, "knowledge/delivery/roadmap.md");
|
|
7270
|
+
const candidates = md ? parseRoadmapCandidates(md) : [];
|
|
7271
|
+
return [{ uri: "kaddo://work-item-candidates", text: JSON.stringify({ candidates }, null, 2), mimeType: "application/json" }];
|
|
7272
|
+
}
|
|
7273
|
+
},
|
|
7274
|
+
{
|
|
7275
|
+
uri: "kaddo://next-step",
|
|
7276
|
+
name: "Kaddo next step",
|
|
7277
|
+
description: "State-aware next-step recommendation for the current delivery state, with parallel (secondary) recommendations and the delivery-state counts behind it (VS-079). Read-only.",
|
|
7278
|
+
mimeType: "application/json",
|
|
7279
|
+
read: (root) => {
|
|
7280
|
+
if (!hasKnowledge(root)) {
|
|
7281
|
+
return text("kaddo://next-step", "Knowledge repository not found. Run `kaddo bootstrap` first.", "text/plain");
|
|
7282
|
+
}
|
|
7283
|
+
const nextStepRecommendation = resolveNextStep(root);
|
|
7284
|
+
const deliveryState = { ...buildDeliveryState(root), phase: nextStepRecommendation.phase };
|
|
7285
|
+
return [{ uri: "kaddo://next-step", text: JSON.stringify({ nextStepRecommendation, deliveryState }, null, 2), mimeType: "application/json" }];
|
|
7286
|
+
}
|
|
7030
7287
|
}
|
|
7031
7288
|
];
|
|
7032
7289
|
|
|
@@ -7321,6 +7578,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
|
|
|
7321
7578
|
nextStep: nextStepRecommendation.label,
|
|
7322
7579
|
recommendedAgents: nextStepRecommendation.agent ? [nextStepRecommendation.agent] : phase.recommendedAgents
|
|
7323
7580
|
};
|
|
7581
|
+
const deliveryState = { ...buildDeliveryState(dir), phase: nextStepRecommendation.phase };
|
|
7324
7582
|
return {
|
|
7325
7583
|
version: CONTEXT_PACK_VERSION,
|
|
7326
7584
|
generatedAt: now.toISOString(),
|
|
@@ -7353,6 +7611,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
|
|
|
7353
7611
|
roadmap,
|
|
7354
7612
|
phase: unifiedPhase,
|
|
7355
7613
|
nextStepRecommendation,
|
|
7614
|
+
deliveryState,
|
|
7356
7615
|
techDecisions,
|
|
7357
7616
|
techKnowledge,
|
|
7358
7617
|
roadmapQuality: buildRoadmapQuality(dir),
|
|
@@ -7417,6 +7676,31 @@ function renderContextPack(pack) {
|
|
|
7417
7676
|
parts.push(`Next step: ${pack.phase.nextStep}
|
|
7418
7677
|
`);
|
|
7419
7678
|
}
|
|
7679
|
+
const ds = pack.deliveryState;
|
|
7680
|
+
const rec = pack.nextStepRecommendation;
|
|
7681
|
+
if (ds.total_work_items > 0 || rec.phase !== "Setup") {
|
|
7682
|
+
parts.push("## Delivery State\n");
|
|
7683
|
+
parts.push(
|
|
7684
|
+
[
|
|
7685
|
+
`- Phase: ${ds.phase}`,
|
|
7686
|
+
`- Draft Work Items: ${ds.draft_work_items}`,
|
|
7687
|
+
`- Ready Work Items: ${ds.ready_work_items}`,
|
|
7688
|
+
`- In-progress Work Items: ${ds.in_progress_work_items}`,
|
|
7689
|
+
`- Ownership coverage: ${ds.ownership_coverage}`,
|
|
7690
|
+
`- Remaining Work Item candidates: ${ds.remaining_work_item_candidates}`
|
|
7691
|
+
].join("\n") + "\n"
|
|
7692
|
+
);
|
|
7693
|
+
parts.push("## Next Step Recommendation\n");
|
|
7694
|
+
const recLines = [`- ${rec.label}`, ` - id: ${rec.id}`, ` - reason: ${rec.reason}`];
|
|
7695
|
+
if (rec.agent) recLines.push(` - agent: ${rec.agent}`);
|
|
7696
|
+
if (rec.skill) recLines.push(` - skill: ${rec.skill}`);
|
|
7697
|
+
if (rec.command) recLines.push(` - command: \`${rec.command}\``);
|
|
7698
|
+
parts.push(recLines.join("\n") + "\n");
|
|
7699
|
+
if (rec.secondary && rec.secondary.length > 0) {
|
|
7700
|
+
parts.push("Also (secondary):\n");
|
|
7701
|
+
parts.push(rec.secondary.map((s) => `- ${s.label}`).join("\n") + "\n");
|
|
7702
|
+
}
|
|
7703
|
+
}
|
|
7420
7704
|
parts.push("## Knowledge Layers\n");
|
|
7421
7705
|
parts.push(
|
|
7422
7706
|
"Project knowledge is organized in four layers: **Business \u2192 Product \u2192 Tech \u2192 Delivery**.\n"
|
|
@@ -7454,35 +7738,52 @@ function renderContextPack(pack) {
|
|
|
7454
7738
|
}
|
|
7455
7739
|
parts.push("## Current Knowledge\n");
|
|
7456
7740
|
parts.push((knowledge.summary || "No project knowledge summary found yet.") + "\n");
|
|
7457
|
-
parts.push("## Roadmap\n");
|
|
7741
|
+
parts.push("## Roadmap Status\n");
|
|
7458
7742
|
if (pack.roadmap.present) {
|
|
7459
7743
|
parts.push(
|
|
7460
7744
|
[
|
|
7461
|
-
`-
|
|
7462
|
-
`-
|
|
7463
|
-
`-
|
|
7745
|
+
`- Initiatives: ${pack.roadmap.initiatives}`,
|
|
7746
|
+
`- Work Item candidates: ${pack.roadmap.work_item_candidates}`,
|
|
7747
|
+
`- Materialized Work Items: ${pack.roadmap.materialized_work_items}`,
|
|
7748
|
+
`- Remaining Work Item candidates: ${pack.roadmap.remaining_work_item_candidates}`
|
|
7464
7749
|
].join("\n") + "\n"
|
|
7465
7750
|
);
|
|
7466
|
-
if (pack.roadmap.
|
|
7751
|
+
if (pack.roadmap.remaining_work_item_candidates > 0) {
|
|
7467
7752
|
parts.push(
|
|
7468
|
-
"
|
|
7753
|
+
"Work Item candidates are not yet Work Items. Materialize them with `kaddo create --from roadmap`.\n"
|
|
7469
7754
|
);
|
|
7470
7755
|
}
|
|
7471
7756
|
}
|
|
7472
7757
|
parts.push((knowledge.roadmapSummary || "No roadmap baseline found.") + "\n");
|
|
7473
7758
|
const rq = pack.roadmapQuality;
|
|
7474
|
-
|
|
7759
|
+
const rqi = rq.initiatives;
|
|
7760
|
+
const rqw = rq.work_item_candidates;
|
|
7761
|
+
if (rqi.total > 0 || rqw.total > 0) {
|
|
7475
7762
|
parts.push("## Roadmap Quality\n");
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
7479
|
-
|
|
7480
|
-
|
|
7481
|
-
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
|
|
7763
|
+
if (rqi.total > 0) {
|
|
7764
|
+
parts.push(
|
|
7765
|
+
[
|
|
7766
|
+
"Initiatives:",
|
|
7767
|
+
`- Candidates evaluated: ${rqi.total}`,
|
|
7768
|
+
`- Grounded: ${rqi.grounded}/${rqi.total}`,
|
|
7769
|
+
`- With related domain: ${rqi.with_related_domain}/${rqi.total}`,
|
|
7770
|
+
`- With related capability: ${rqi.with_related_capability}/${rqi.total}`,
|
|
7771
|
+
`- With source signals: ${rqi.with_source_signals}/${rqi.total}`
|
|
7772
|
+
].join("\n") + "\n"
|
|
7773
|
+
);
|
|
7774
|
+
}
|
|
7775
|
+
if (rqw.total > 0) {
|
|
7776
|
+
parts.push(
|
|
7777
|
+
[
|
|
7778
|
+
"Work Item Candidates:",
|
|
7779
|
+
`- Candidates: ${rqw.total}`,
|
|
7780
|
+
`- With source initiative: ${rqw.with_source_initiative}/${rqw.total}`,
|
|
7781
|
+
`- With related domain: ${rqw.with_related_domain}/${rqw.total}`,
|
|
7782
|
+
`- With related capability: ${rqw.with_related_capability}/${rqw.total}`
|
|
7783
|
+
].join("\n") + "\n"
|
|
7784
|
+
);
|
|
7785
|
+
}
|
|
7786
|
+
if (rqi.needs_refinement) {
|
|
7486
7787
|
parts.push(
|
|
7487
7788
|
"Roadmap quality: needs refinement. Use the roadmap-agent to add domain / capability / source signals.\n"
|
|
7488
7789
|
);
|
package/package.json
CHANGED