@kaddo/cli 3.74.1 → 3.76.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/admin-dist/assets/index-BPQdYfAp.css +2 -0
- package/dist/admin-dist/assets/index-BuxKrty5.js +44 -0
- package/dist/admin-dist/index.html +2 -2
- package/dist/admin-server/index.js +63 -323
- package/dist/core.js +444 -122
- package/package.json +1 -1
- package/dist/admin-dist/assets/index-AAaxSvT4.js +0 -38
- package/dist/admin-dist/assets/index-BPt-9--k.css +0 -2
package/dist/core.js
CHANGED
|
@@ -1625,6 +1625,150 @@ function loadExternalCapsules(dir) {
|
|
|
1625
1625
|
}
|
|
1626
1626
|
|
|
1627
1627
|
// src/core/graph.ts
|
|
1628
|
+
var KNOWLEDGE2 = "knowledge";
|
|
1629
|
+
var ACTIVE_STATUSES = ["draft", "ready", "in-progress", "blocked"];
|
|
1630
|
+
var ALL_STATUSES = ["draft", "ready", "in-progress", "blocked", "completed"];
|
|
1631
|
+
function scopeStatuses(scope) {
|
|
1632
|
+
return scope === "all" ? { included: ALL_STATUSES, excluded: ["archived"] } : { included: ACTIVE_STATUSES, excluded: ["completed", "archived"] };
|
|
1633
|
+
}
|
|
1634
|
+
function toPosix2(p2) {
|
|
1635
|
+
return p2.replace(/\\/g, "/");
|
|
1636
|
+
}
|
|
1637
|
+
function slug(s) {
|
|
1638
|
+
return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1639
|
+
}
|
|
1640
|
+
function isAdr(a) {
|
|
1641
|
+
return toPosix2(a.filePath).includes("/tech/decisions/") && Boolean(a.type);
|
|
1642
|
+
}
|
|
1643
|
+
function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
1644
|
+
const scope = opts.scope ?? "active";
|
|
1645
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
1646
|
+
const edges = [];
|
|
1647
|
+
const edgeKeys = /* @__PURE__ */ new Set();
|
|
1648
|
+
const addNode = (node) => {
|
|
1649
|
+
if (!nodes.has(node.id)) nodes.set(node.id, node);
|
|
1650
|
+
};
|
|
1651
|
+
const addEdge = (from, to, type) => {
|
|
1652
|
+
const key = `${from}|${to}|${type}`;
|
|
1653
|
+
if (edgeKeys.has(key)) return;
|
|
1654
|
+
edgeKeys.add(key);
|
|
1655
|
+
edges.push({ from, to, type });
|
|
1656
|
+
};
|
|
1657
|
+
const layerDocs = [
|
|
1658
|
+
{ id: "business:business", type: "business", label: "Business", files: ["business/business.md"] },
|
|
1659
|
+
{ id: "product:product", type: "product", label: "Product", files: ["product/product.md", "product/capabilities.md"] },
|
|
1660
|
+
{ id: "tech:tech", type: "tech", label: "Tech", files: ["tech/current-state.md", "tech/codebase.md"] },
|
|
1661
|
+
{ id: "delivery:delivery", type: "delivery", label: "Delivery", files: ["delivery/roadmap.md"] }
|
|
1662
|
+
];
|
|
1663
|
+
const presentLayers = [];
|
|
1664
|
+
for (const layer of layerDocs) {
|
|
1665
|
+
const path3 = layer.files.map((f) => `${KNOWLEDGE2}/${f}`).find((rel) => exists(join(dir, rel)));
|
|
1666
|
+
if (path3) {
|
|
1667
|
+
addNode({ id: layer.id, type: layer.type, label: layer.label, path: path3 });
|
|
1668
|
+
presentLayers.push(layer.id);
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
for (let i = 0; i < presentLayers.length - 1; i++) {
|
|
1672
|
+
addEdge(presentLayers[i], presentLayers[i + 1], "informs");
|
|
1673
|
+
}
|
|
1674
|
+
const all = discoverKnowledge(dir);
|
|
1675
|
+
const workItems = all.filter((a) => a.isWorkItem);
|
|
1676
|
+
const { included, excluded } = scopeStatuses(scope);
|
|
1677
|
+
const includedSet = new Set(included);
|
|
1678
|
+
const activeWICount = workItems.filter((a) => a.lifecycle && isActiveState(a.lifecycle)).length;
|
|
1679
|
+
const selectedWIs = workItems.filter((a) => a.lifecycle && includedSet.has(a.lifecycle));
|
|
1680
|
+
for (const wi of selectedWIs) {
|
|
1681
|
+
const id = wi.id || wi.title;
|
|
1682
|
+
if (!id || !id.trim()) continue;
|
|
1683
|
+
const wiNodeId = `wi:${id}`;
|
|
1684
|
+
addNode({
|
|
1685
|
+
id: wiNodeId,
|
|
1686
|
+
type: "work-item",
|
|
1687
|
+
label: `${id} ${wi.title}`.trim(),
|
|
1688
|
+
path: wi.relPath,
|
|
1689
|
+
status: wi.lifecycle,
|
|
1690
|
+
knowledge_level: wi.knowledgeLevel || void 0
|
|
1691
|
+
});
|
|
1692
|
+
for (const glob of wi.codeGlobs) {
|
|
1693
|
+
if (!glob || !glob.trim()) continue;
|
|
1694
|
+
const codeId = `code:${glob}`;
|
|
1695
|
+
addNode({ id: codeId, type: "code-glob", label: glob });
|
|
1696
|
+
addEdge(wiNodeId, codeId, "owns");
|
|
1697
|
+
}
|
|
1698
|
+
for (const cap of wi.capabilities) {
|
|
1699
|
+
if (!cap || !cap.trim()) continue;
|
|
1700
|
+
const capId = `capability:${slug(cap) || cap}`;
|
|
1701
|
+
addNode({ id: capId, type: "capability", label: cap });
|
|
1702
|
+
addEdge(wiNodeId, capId, "implements");
|
|
1703
|
+
}
|
|
1704
|
+
for (const dec of wi.decisions) {
|
|
1705
|
+
if (!dec || !dec.trim()) continue;
|
|
1706
|
+
const adrId = `adr:${dec}`;
|
|
1707
|
+
addNode({ id: adrId, type: "decision", label: dec });
|
|
1708
|
+
addEdge(wiNodeId, adrId, "depends_on");
|
|
1709
|
+
}
|
|
1710
|
+
if (wi.initiative) {
|
|
1711
|
+
const initId = `initiative:${slug(wi.initiative) || wi.initiative}`;
|
|
1712
|
+
addNode({ id: initId, type: "initiative", label: wi.initiative });
|
|
1713
|
+
addEdge(wiNodeId, initId, "belongs_to");
|
|
1714
|
+
}
|
|
1715
|
+
if (wi.source === "roadmap" && wi.sourceId) {
|
|
1716
|
+
const candId = `candidate:${wi.sourceId}`;
|
|
1717
|
+
addNode({ id: candId, type: "roadmap-candidate", label: wi.sourceId });
|
|
1718
|
+
addEdge(candId, wiNodeId, "materialized_as");
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
for (const adr of all.filter(isAdr)) {
|
|
1722
|
+
const adrLabel = adr.id || adr.title;
|
|
1723
|
+
if (!adrLabel || !adrLabel.trim()) continue;
|
|
1724
|
+
const adrId = `adr:${adrLabel}`;
|
|
1725
|
+
const referenced = nodes.has(adrId);
|
|
1726
|
+
if (scope === "all" || referenced) {
|
|
1727
|
+
nodes.set(adrId, {
|
|
1728
|
+
id: adrId,
|
|
1729
|
+
type: "decision",
|
|
1730
|
+
label: `${adr.id} ${adr.title}`.trim() || adrLabel,
|
|
1731
|
+
path: adr.relPath
|
|
1732
|
+
});
|
|
1733
|
+
for (const glob of adr.codeGlobs) {
|
|
1734
|
+
if (!glob || !glob.trim()) continue;
|
|
1735
|
+
const codeId = `code:${glob}`;
|
|
1736
|
+
addNode({ id: codeId, type: "code-glob", label: glob });
|
|
1737
|
+
addEdge(adrId, codeId, "governs");
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
const capsules = loadExternalRegistry(dir);
|
|
1742
|
+
if (capsules.length > 0) {
|
|
1743
|
+
const projId = `project:${slug(config.project.name) || "project"}`;
|
|
1744
|
+
addNode({ id: projId, type: "project", label: config.project.name });
|
|
1745
|
+
for (const cap of capsules) {
|
|
1746
|
+
const capId = `capsule:${cap.id}`;
|
|
1747
|
+
addNode({ id: capId, type: "knowledge-capsule", label: cap.id, path: cap.path });
|
|
1748
|
+
addEdge(capId, projId, "provides_external_context");
|
|
1749
|
+
for (const wi of selectedWIs) {
|
|
1750
|
+
if (wi.capsules.includes(cap.id)) {
|
|
1751
|
+
addEdge(`wi:${wi.id || wi.title}`, capId, "uses_external_knowledge");
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
const scopeReason = scope === "all" ? "All supported Work Item statuses are included." : activeWICount === 0 ? "No active Work Items found. Completed Work Items are excluded from active scope." : "Active Work Items only; completed and archived are excluded.";
|
|
1757
|
+
return {
|
|
1758
|
+
generated_at: now.toISOString(),
|
|
1759
|
+
project: {
|
|
1760
|
+
name: config.project.name,
|
|
1761
|
+
state: config.project.state,
|
|
1762
|
+
structure: config.project.structure
|
|
1763
|
+
},
|
|
1764
|
+
scope,
|
|
1765
|
+
scope_reason: scopeReason,
|
|
1766
|
+
included_statuses: included,
|
|
1767
|
+
excluded_statuses: excluded,
|
|
1768
|
+
nodes: [...nodes.values()],
|
|
1769
|
+
edges
|
|
1770
|
+
};
|
|
1771
|
+
}
|
|
1628
1772
|
var ACTIVE_WI_STATES = /* @__PURE__ */ new Set(["draft", "ready", "in-progress", "blocked"]);
|
|
1629
1773
|
function loadGraphSummary(dir) {
|
|
1630
1774
|
const p2 = join(dir, ".kaddo", "graph.json");
|
|
@@ -1656,6 +1800,161 @@ function loadGraphSummary(dir) {
|
|
|
1656
1800
|
}
|
|
1657
1801
|
|
|
1658
1802
|
// src/core/graph-hints.ts
|
|
1803
|
+
var KNOWLEDGE3 = "knowledge";
|
|
1804
|
+
function toPosix3(p2) {
|
|
1805
|
+
return p2.replace(/\\/g, "/");
|
|
1806
|
+
}
|
|
1807
|
+
function slug2(s) {
|
|
1808
|
+
return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1809
|
+
}
|
|
1810
|
+
function isAdr2(a) {
|
|
1811
|
+
return toPosix3(a.filePath).includes("/tech/decisions/") && Boolean(a.type);
|
|
1812
|
+
}
|
|
1813
|
+
function capabilityHeadings(dir) {
|
|
1814
|
+
const p2 = join(dir, KNOWLEDGE3, "product", "capabilities.md");
|
|
1815
|
+
if (!exists(p2)) return [];
|
|
1816
|
+
return readFile(p2).split(/\r?\n/).map((l) => l.match(/^#{2,3}\s+(.+?)\s*$/)).filter((m) => Boolean(m)).map((m) => m[1].trim()).filter((h) => !/^(summary|resumen|overview|capabilities|capacidades)$/i.test(h));
|
|
1817
|
+
}
|
|
1818
|
+
function humanMissing(field) {
|
|
1819
|
+
switch (field) {
|
|
1820
|
+
case "code":
|
|
1821
|
+
return "code ownership";
|
|
1822
|
+
case "capabilities":
|
|
1823
|
+
return "linked capability";
|
|
1824
|
+
case "decisions":
|
|
1825
|
+
return "linked decision";
|
|
1826
|
+
case "source":
|
|
1827
|
+
return "roadmap link";
|
|
1828
|
+
default:
|
|
1829
|
+
return field;
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
function buildGraphHints(dir, graph, now = /* @__PURE__ */ new Date()) {
|
|
1833
|
+
const artifacts = discoverKnowledge(dir);
|
|
1834
|
+
const workItems = artifacts.filter((a) => a.isWorkItem);
|
|
1835
|
+
const activeWIs = workItems.filter((a) => a.lifecycle && isActiveState(a.lifecycle));
|
|
1836
|
+
const adrs = artifacts.filter(isAdr2);
|
|
1837
|
+
const capsules = loadExternalRegistry(dir);
|
|
1838
|
+
const hints = [];
|
|
1839
|
+
const referencedCapabilitySlugs = new Set(
|
|
1840
|
+
workItems.flatMap((w) => w.capabilities.map((c) => slug2(c)))
|
|
1841
|
+
);
|
|
1842
|
+
const referencedCapsuleIds = new Set(workItems.flatMap((w) => w.capsules.map((c) => c)));
|
|
1843
|
+
let activeWithoutCode = 0;
|
|
1844
|
+
let activeWithoutCapabilities = 0;
|
|
1845
|
+
let wisWithoutSource = 0;
|
|
1846
|
+
for (const wi of activeWIs) {
|
|
1847
|
+
const id = wi.id || wi.title;
|
|
1848
|
+
const missing = [];
|
|
1849
|
+
const suggested = {};
|
|
1850
|
+
if (wi.codeGlobs.length === 0) {
|
|
1851
|
+
missing.push("code");
|
|
1852
|
+
suggested.code = ["src/<area>/**"];
|
|
1853
|
+
activeWithoutCode++;
|
|
1854
|
+
}
|
|
1855
|
+
if (wi.capabilities.length === 0) {
|
|
1856
|
+
missing.push("capabilities");
|
|
1857
|
+
suggested.capabilities = ["<capability>"];
|
|
1858
|
+
activeWithoutCapabilities++;
|
|
1859
|
+
}
|
|
1860
|
+
if (wi.decisions.length === 0) {
|
|
1861
|
+
missing.push("decisions");
|
|
1862
|
+
suggested.decisions = ["ADR-XXX"];
|
|
1863
|
+
}
|
|
1864
|
+
const hasSource = Boolean(wi.sourceId) || Boolean(wi.initiative);
|
|
1865
|
+
if (!hasSource) {
|
|
1866
|
+
missing.push("source");
|
|
1867
|
+
wisWithoutSource++;
|
|
1868
|
+
}
|
|
1869
|
+
if (missing.length === 0) continue;
|
|
1870
|
+
hints.push({
|
|
1871
|
+
artifact_id: id,
|
|
1872
|
+
artifact_type: "work-item",
|
|
1873
|
+
path: wi.relPath,
|
|
1874
|
+
severity: "info",
|
|
1875
|
+
missing,
|
|
1876
|
+
reason: "Active Work Item has limited graph relationships.",
|
|
1877
|
+
message: `${id} has no ${missing.map(humanMissing).join(", ")}.`,
|
|
1878
|
+
suggested_front_matter: Object.keys(suggested).length > 0 ? suggested : void 0
|
|
1879
|
+
});
|
|
1880
|
+
}
|
|
1881
|
+
let adrsWithoutCode = 0;
|
|
1882
|
+
for (const adr of adrs) {
|
|
1883
|
+
if (adr.codeGlobs.length > 0) continue;
|
|
1884
|
+
adrsWithoutCode++;
|
|
1885
|
+
const id = adr.id || adr.title;
|
|
1886
|
+
hints.push({
|
|
1887
|
+
artifact_id: id,
|
|
1888
|
+
artifact_type: "decision",
|
|
1889
|
+
path: adr.relPath,
|
|
1890
|
+
severity: "info",
|
|
1891
|
+
missing: ["code"],
|
|
1892
|
+
reason: "This ADR defines technical decisions but does not declare which paths it governs.",
|
|
1893
|
+
message: `${id} has no governed code paths.`,
|
|
1894
|
+
suggested_front_matter: { code: ["<path/to/code>"] }
|
|
1895
|
+
});
|
|
1896
|
+
}
|
|
1897
|
+
for (const cap of capabilityHeadings(dir)) {
|
|
1898
|
+
if (referencedCapabilitySlugs.has(slug2(cap))) continue;
|
|
1899
|
+
hints.push({
|
|
1900
|
+
artifact_id: cap,
|
|
1901
|
+
artifact_type: "capability",
|
|
1902
|
+
severity: "info",
|
|
1903
|
+
missing: ["work-item"],
|
|
1904
|
+
reason: "This capability is declared but no Work Item references it yet.",
|
|
1905
|
+
message: `Capability "${cap}" is not linked to any Work Item.`
|
|
1906
|
+
});
|
|
1907
|
+
}
|
|
1908
|
+
let capsulesWithoutWi = 0;
|
|
1909
|
+
for (const cap of capsules) {
|
|
1910
|
+
if (referencedCapsuleIds.has(cap.id)) continue;
|
|
1911
|
+
capsulesWithoutWi++;
|
|
1912
|
+
hints.push({
|
|
1913
|
+
artifact_id: cap.id,
|
|
1914
|
+
artifact_type: "knowledge-capsule",
|
|
1915
|
+
path: cap.path,
|
|
1916
|
+
severity: "info",
|
|
1917
|
+
missing: ["work-item"],
|
|
1918
|
+
reason: "This Knowledge Capsule is available but no Work Item declares `capsules:` for it.",
|
|
1919
|
+
message: `Knowledge Capsule "${cap.id}" is not linked to any Work Item.`
|
|
1920
|
+
});
|
|
1921
|
+
}
|
|
1922
|
+
const inEdge = /* @__PURE__ */ new Set();
|
|
1923
|
+
for (const e of graph.edges) {
|
|
1924
|
+
inEdge.add(e.from);
|
|
1925
|
+
inEdge.add(e.to);
|
|
1926
|
+
}
|
|
1927
|
+
const nodes = graph.nodes.length;
|
|
1928
|
+
const connected = graph.nodes.filter((n) => inEdge.has(n.id)).length;
|
|
1929
|
+
const relationshipEdges = graph.edges.filter((e) => e.type !== "informs").length;
|
|
1930
|
+
const metrics = {
|
|
1931
|
+
nodes_count: nodes,
|
|
1932
|
+
edges_count: graph.edges.length,
|
|
1933
|
+
connected_nodes_count: connected,
|
|
1934
|
+
isolated_nodes_count: nodes - connected,
|
|
1935
|
+
active_work_items_without_code: activeWithoutCode,
|
|
1936
|
+
active_work_items_without_capabilities: activeWithoutCapabilities,
|
|
1937
|
+
work_items_without_source: wisWithoutSource,
|
|
1938
|
+
adrs_without_code: adrsWithoutCode,
|
|
1939
|
+
capsules_without_related_work_items: capsulesWithoutWi
|
|
1940
|
+
};
|
|
1941
|
+
const quality = assessQuality(nodes, relationshipEdges, hints.length);
|
|
1942
|
+
return {
|
|
1943
|
+
generated_at: now.toISOString(),
|
|
1944
|
+
scope: graph.scope,
|
|
1945
|
+
scope_reason: graph.scope_reason,
|
|
1946
|
+
quality,
|
|
1947
|
+
summary: { nodes, edges: graph.edges.length, hints: hints.length },
|
|
1948
|
+
metrics,
|
|
1949
|
+
hints
|
|
1950
|
+
};
|
|
1951
|
+
}
|
|
1952
|
+
function assessQuality(nodes, relationshipEdges, hintCount) {
|
|
1953
|
+
if (nodes === 0 || relationshipEdges === 0) return "empty";
|
|
1954
|
+
if (relationshipEdges / nodes < 0.25) return "sparse";
|
|
1955
|
+
if (hintCount > 0) return "partial";
|
|
1956
|
+
return "good";
|
|
1957
|
+
}
|
|
1659
1958
|
function loadGraphHints(dir) {
|
|
1660
1959
|
const p2 = join(dir, ".kaddo", "graph-hints.json");
|
|
1661
1960
|
if (!exists(p2)) return null;
|
|
@@ -1708,7 +2007,7 @@ function skillGroupCounts(skills) {
|
|
|
1708
2007
|
}
|
|
1709
2008
|
|
|
1710
2009
|
// src/core/knowledge-discovery.ts
|
|
1711
|
-
var
|
|
2010
|
+
var KNOWLEDGE4 = "knowledge";
|
|
1712
2011
|
var CONSOLIDATED_TYPE = {
|
|
1713
2012
|
Business: "business",
|
|
1714
2013
|
Product: "product",
|
|
@@ -1750,10 +2049,10 @@ function layerForType(type) {
|
|
|
1750
2049
|
}
|
|
1751
2050
|
function layerFromPath(filePath) {
|
|
1752
2051
|
const p2 = filePath.replace(/\\/g, "/");
|
|
1753
|
-
if (p2.includes(`/${
|
|
1754
|
-
if (p2.includes(`/${
|
|
1755
|
-
if (p2.includes(`/${
|
|
1756
|
-
if (p2.includes(`/${
|
|
2052
|
+
if (p2.includes(`/${KNOWLEDGE4}/business/`)) return "Business";
|
|
2053
|
+
if (p2.includes(`/${KNOWLEDGE4}/product/`)) return "Product";
|
|
2054
|
+
if (p2.includes(`/${KNOWLEDGE4}/tech/`)) return "Tech";
|
|
2055
|
+
if (p2.includes(`/${KNOWLEDGE4}/delivery/`)) return "Delivery";
|
|
1757
2056
|
return null;
|
|
1758
2057
|
}
|
|
1759
2058
|
function basename(p2) {
|
|
@@ -1766,7 +2065,7 @@ function discoverLayers(dir) {
|
|
|
1766
2065
|
Tech: blank(),
|
|
1767
2066
|
Delivery: blank()
|
|
1768
2067
|
};
|
|
1769
|
-
const archDir = join(dir,
|
|
2068
|
+
const archDir = join(dir, KNOWLEDGE4);
|
|
1770
2069
|
const artifacts = exists(archDir) ? readArtifacts(archDir) : [];
|
|
1771
2070
|
for (const a of artifacts) {
|
|
1772
2071
|
const type = a.type;
|
|
@@ -1788,7 +2087,7 @@ function discoverLayers(dir) {
|
|
|
1788
2087
|
}
|
|
1789
2088
|
if (type === "adr" || type === "decision") slot.hasDecision = true;
|
|
1790
2089
|
}
|
|
1791
|
-
if (existsDirWithMd(join(dir,
|
|
2090
|
+
if (existsDirWithMd(join(dir, KNOWLEDGE4, "tech", "decisions"))) acc.Tech.structured = true;
|
|
1792
2091
|
return ["Business", "Product", "Tech", "Delivery"].map((layer) => ({
|
|
1793
2092
|
layer,
|
|
1794
2093
|
status: statusFor(layer, acc[layer]),
|
|
@@ -3686,9 +3985,6 @@ var RECOMMENDED_SKILLS = [...SKILL_GROUPS.delivery, ...SKILL_GROUPS.tech];
|
|
|
3686
3985
|
function skillInstallPath(id) {
|
|
3687
3986
|
return `knowledge/skills/${id}/skill.md`;
|
|
3688
3987
|
}
|
|
3689
|
-
function skillById(id) {
|
|
3690
|
-
return SKILLS.find((s) => s.id === id);
|
|
3691
|
-
}
|
|
3692
3988
|
|
|
3693
3989
|
// src/agents/responsibility.ts
|
|
3694
3990
|
var RESPONSIBILITY_MATRIX = {
|
|
@@ -7456,6 +7752,17 @@ function analyzeCrossRepoEvidence(input) {
|
|
|
7456
7752
|
|
|
7457
7753
|
// src/core/work-items.ts
|
|
7458
7754
|
import matter6 from "gray-matter";
|
|
7755
|
+
function computeRefinementStatus(wi) {
|
|
7756
|
+
const aspects = {
|
|
7757
|
+
outcome: Boolean(wi.currentBehavior?.trim() || wi.targetBehavior?.trim()),
|
|
7758
|
+
journey: Boolean(wi.entryPoints?.trim() || wi.endToEndFlow?.trim()),
|
|
7759
|
+
modules: wi.affectedModules.length > 0 || wi.moduleCoverage.length > 0,
|
|
7760
|
+
impact: wi.impactAnalysis.length > 0,
|
|
7761
|
+
acceptance: wi.acceptanceCriteria.length > 0
|
|
7762
|
+
};
|
|
7763
|
+
const refined = aspects.outcome && aspects.modules && aspects.acceptance;
|
|
7764
|
+
return { status: refined ? "refined" : "needs-refinement", aspects };
|
|
7765
|
+
}
|
|
7459
7766
|
var WorkItemNotFoundError = class extends Error {
|
|
7460
7767
|
constructor(workItemId) {
|
|
7461
7768
|
super(`Work Item "${workItemId}" was not found.`);
|
|
@@ -7531,8 +7838,9 @@ function getWorkItem(dir, workItemId) {
|
|
|
7531
7838
|
const fm = match.rawFrontmatter;
|
|
7532
7839
|
const knowledge = discoverKnowledge(dir).filter((a) => !a.isWorkItem);
|
|
7533
7840
|
const knowledgeById = new Map(knowledge.filter((k) => k.id).map((k) => [k.id, k]));
|
|
7534
|
-
|
|
7841
|
+
const detail = {
|
|
7535
7842
|
...base,
|
|
7843
|
+
summary: match.summary?.trim() || null,
|
|
7536
7844
|
actor: sectionText(sections, ["actor"]),
|
|
7537
7845
|
outcome: sectionText(sections, ["actor and outcome", "outcome", "expected result"]),
|
|
7538
7846
|
currentBehavior: sectionText(sections, ["current behavior", "current behaviour"]),
|
|
@@ -7552,6 +7860,7 @@ function getWorkItem(dir, workItemId) {
|
|
|
7552
7860
|
source: parseWorkItemSource(fm),
|
|
7553
7861
|
path: match.relPath
|
|
7554
7862
|
};
|
|
7863
|
+
return { ...detail, refinement: computeRefinementStatus(detail) };
|
|
7555
7864
|
}
|
|
7556
7865
|
function readBody(filePath) {
|
|
7557
7866
|
try {
|
|
@@ -8354,140 +8663,154 @@ function getWorkItemCaptureDefinition() {
|
|
|
8354
8663
|
questions
|
|
8355
8664
|
};
|
|
8356
8665
|
}
|
|
8357
|
-
|
|
8358
|
-
|
|
8359
|
-
|
|
8360
|
-
|
|
8361
|
-
}
|
|
8362
|
-
function assembleRefinementContext(dir, workItemId) {
|
|
8363
|
-
const edit = getWorkItemForEdit(dir, workItemId);
|
|
8666
|
+
var RECOMMENDED_AGENT = "work-item-agent";
|
|
8667
|
+
var RECOMMENDED_SKILL = "work-item-refinement";
|
|
8668
|
+
function buildRefinementHandoff(dir, workItemId) {
|
|
8669
|
+
const wi = getWorkItem(dir, workItemId);
|
|
8364
8670
|
const config = loadConfig(dir);
|
|
8365
|
-
const
|
|
8366
|
-
|
|
8671
|
+
const projectName = config?.project.name ?? "this project";
|
|
8672
|
+
const mappedModules = loadMappedModules(dir).map((m) => m.id);
|
|
8673
|
+
const multirepo = mappedModules.length > 0;
|
|
8674
|
+
const lines = [
|
|
8675
|
+
`Refine Work Item ${wi.id} \u2014 "${wi.title}" \u2014 in project "${projectName}" using Kaddo.`,
|
|
8676
|
+
"",
|
|
8677
|
+
"Use a Kaddo-enabled agent with access to this repository. Drive the refinement with the",
|
|
8678
|
+
`canonical ${RECOMMENDED_AGENT} and the ${RECOMMENDED_SKILL} skill (via Kaddo MCP or skills).`,
|
|
8679
|
+
"",
|
|
8680
|
+
"Inspect the actual implementation before defining scope \u2014 do not guess affected modules from",
|
|
8681
|
+
"the Work Item title. Read the current behavior in the code first, then classify."
|
|
8682
|
+
];
|
|
8683
|
+
if (multirepo) {
|
|
8684
|
+
lines.push(
|
|
8685
|
+
"",
|
|
8686
|
+
`This is a multirepo project. Evaluate the scope across all relevant mapped modules (${mappedModules.join(", ")})`,
|
|
8687
|
+
"before finalizing affected_modules and module_coverage."
|
|
8688
|
+
);
|
|
8689
|
+
}
|
|
8690
|
+
lines.push(
|
|
8691
|
+
"",
|
|
8692
|
+
`Update the canonical Work Item ${wi.id} with:`,
|
|
8693
|
+
"- current and target behavior;",
|
|
8694
|
+
"- the end-to-end flow (journey);",
|
|
8695
|
+
"- affected modules;",
|
|
8696
|
+
"- module coverage;",
|
|
8697
|
+
"- impact analysis across the relevant surfaces;",
|
|
8698
|
+
"- scope confidence and open unknowns;",
|
|
8699
|
+
"- acceptance criteria;",
|
|
8700
|
+
"- relevant Knowledge / ADR relationships.",
|
|
8701
|
+
"",
|
|
8702
|
+
"Do not implement the Work Item. Do not run mutating Git operations."
|
|
8367
8703
|
);
|
|
8368
|
-
const decisions = knowledgeArtifacts.filter((a) => a.type === "adr" || /^adr-/i.test(a.id)).map((a) => ({ id: a.id, title: a.title || a.id }));
|
|
8369
|
-
const knowledge = knowledgeArtifacts.filter((a) => !(a.type === "adr" || /^adr-/i.test(a.id))).map((a) => ({ id: a.id || a.relPath, title: a.title || a.id, layer: a.layer, type: a.type || void 0, summary: a.summary || void 0 }));
|
|
8370
|
-
const modules = ["core", ...loadMappedModules(dir).map((m) => m.id)].filter((v, i, arr) => arr.indexOf(v) === i);
|
|
8371
8704
|
return {
|
|
8372
|
-
|
|
8373
|
-
|
|
8374
|
-
|
|
8375
|
-
|
|
8376
|
-
|
|
8377
|
-
|
|
8705
|
+
workItemId: wi.id,
|
|
8706
|
+
title: wi.title,
|
|
8707
|
+
projectName,
|
|
8708
|
+
refinement: wi.refinement,
|
|
8709
|
+
recommendedAgent: RECOMMENDED_AGENT,
|
|
8710
|
+
recommendedSkill: RECOMMENDED_SKILL,
|
|
8711
|
+
text: lines.join("\n")
|
|
8378
8712
|
};
|
|
8379
8713
|
}
|
|
8380
|
-
|
|
8381
|
-
|
|
8382
|
-
|
|
8383
|
-
|
|
8384
|
-
|
|
8385
|
-
|
|
8386
|
-
|
|
8387
|
-
|
|
8388
|
-
|
|
8389
|
-
|
|
8390
|
-
|
|
8391
|
-
|
|
8392
|
-
|
|
8393
|
-
|
|
8394
|
-
|
|
8395
|
-
|
|
8396
|
-
|
|
8397
|
-
|
|
8398
|
-
|
|
8399
|
-
|
|
8400
|
-
|
|
8401
|
-
if (str(o.actor) !== void 0) input.actor = str(o.actor);
|
|
8402
|
-
if (str(o.observableOutcome) !== void 0) input.outcome = str(o.observableOutcome);
|
|
8403
|
-
if (str(o.currentBehavior) !== void 0) input.currentBehavior = str(o.currentBehavior);
|
|
8404
|
-
if (str(o.targetBehavior) !== void 0) input.targetBehavior = str(o.targetBehavior);
|
|
8405
|
-
const j = proposal.journey ?? {};
|
|
8406
|
-
if (j.entryPoints) input.entryPoints = strList(j.entryPoints).join("\n");
|
|
8407
|
-
if (j.flow) input.endToEndFlow = strList(j.flow).map((s) => `- ${s}`).join("\n");
|
|
8408
|
-
if (proposal.moduleCoverage) {
|
|
8409
|
-
input.moduleCoverage = proposal.moduleCoverage.filter((c) => {
|
|
8410
|
-
if (!knownModules.has(c.id)) {
|
|
8411
|
-
extraFindings.push({ level: "warning", message: `Proposed module "${c.id}" is not registered and was not included.` });
|
|
8412
|
-
return false;
|
|
8413
|
-
}
|
|
8414
|
-
return VALID_COVERAGE2.has(c.status);
|
|
8415
|
-
}).map((c) => ({ id: c.id, status: c.status, ...str(c.reason) ? { reason: str(c.reason) } : {} }));
|
|
8416
|
-
}
|
|
8417
|
-
const affected = /* @__PURE__ */ new Set();
|
|
8418
|
-
for (const m of proposal.affectedModules ?? []) if (knownModules.has(m)) affected.add(m);
|
|
8419
|
-
for (const c of input.moduleCoverage) if (c.status === "affected") affected.add(c.id);
|
|
8420
|
-
if (proposal.affectedModules || proposal.moduleCoverage) input.affectedModules = [...affected];
|
|
8421
|
-
if (proposal.impactAnalysis) {
|
|
8422
|
-
input.impactAnalysis = proposal.impactAnalysis.filter((s) => VALID_COVERAGE2.has(s.status) && str(s.surface)).map((s) => ({ surface: str(s.surface), status: s.status, ...str(s.reason) ? { reason: str(s.reason) } : {}, ...str(s.question) ? { question: str(s.question) } : {} }));
|
|
8423
|
-
}
|
|
8424
|
-
if (proposal.scopeConfidence && VALID_CONFIDENCE2.has(proposal.scopeConfidence.level)) {
|
|
8425
|
-
input.scopeConfidence = { level: proposal.scopeConfidence.level, reasons: strList(proposal.scopeConfidence.reasons) };
|
|
8426
|
-
}
|
|
8427
|
-
if (proposal.scopeUnknowns) input.scopeUnknowns = strList(proposal.scopeUnknowns);
|
|
8428
|
-
if (proposal.acceptanceCriteria) input.acceptanceCriteria = strList(proposal.acceptanceCriteria).map((t) => ({ text: t, checked: null }));
|
|
8429
|
-
if (proposal.linkedDecisions) {
|
|
8430
|
-
input.decisions = proposal.linkedDecisions.filter((id) => {
|
|
8431
|
-
if (!knownDecisions.has(id)) {
|
|
8432
|
-
extraFindings.push({ level: "warning", message: `Proposed decision "${id}" does not exist and was not linked.` });
|
|
8433
|
-
return false;
|
|
8434
|
-
}
|
|
8435
|
-
return true;
|
|
8436
|
-
});
|
|
8437
|
-
}
|
|
8438
|
-
if (proposal.relatedKnowledge) {
|
|
8439
|
-
input.relatedKnowledge = proposal.relatedKnowledge.filter((id) => {
|
|
8440
|
-
if (!knownKnowledge.has(id)) {
|
|
8441
|
-
extraFindings.push({ level: "warning", message: `Proposed knowledge "${id}" could not be resolved and was not linked.` });
|
|
8442
|
-
return false;
|
|
8443
|
-
}
|
|
8444
|
-
return true;
|
|
8445
|
-
});
|
|
8446
|
-
}
|
|
8447
|
-
const findings = [...extraFindings, ...evaluate(input, knownModules)];
|
|
8448
|
-
const blocking = findings.filter((f) => f.level === "blocking").length;
|
|
8449
|
-
const warning = findings.filter((f) => f.level === "warning").length;
|
|
8450
|
-
const fyi = findings.filter((f) => f.level === "fyi").length;
|
|
8451
|
-
return { input, validation: { findings, blocking, warning, fyi, canApply: true } };
|
|
8714
|
+
|
|
8715
|
+
// src/core/system-map.ts
|
|
8716
|
+
var EDGE_LABELS = {
|
|
8717
|
+
informs: "informs",
|
|
8718
|
+
belongs_to: "belongs to",
|
|
8719
|
+
materialized_as: "materialized as",
|
|
8720
|
+
owns: "owns",
|
|
8721
|
+
implements: "implements",
|
|
8722
|
+
depends_on: "depends on",
|
|
8723
|
+
governs: "governs",
|
|
8724
|
+
provides_external_context: "provides external context",
|
|
8725
|
+
uses_external_knowledge: "uses external knowledge"
|
|
8726
|
+
};
|
|
8727
|
+
function emptyProjection(name, structure) {
|
|
8728
|
+
return {
|
|
8729
|
+
system: { name },
|
|
8730
|
+
nodes: [],
|
|
8731
|
+
relationships: [],
|
|
8732
|
+
groups: [],
|
|
8733
|
+
metadata: { projectName: name, structure, nodeCount: 0, relationshipCount: 0, coverage: "empty", available: false }
|
|
8734
|
+
};
|
|
8452
8735
|
}
|
|
8453
|
-
function
|
|
8454
|
-
const
|
|
8455
|
-
|
|
8456
|
-
|
|
8457
|
-
|
|
8736
|
+
function getSystemMapProjection(dir) {
|
|
8737
|
+
const config = loadConfig(dir);
|
|
8738
|
+
if (!config) return emptyProjection("unknown", "unknown");
|
|
8739
|
+
const graph = buildGraph(dir, config, { scope: "all" });
|
|
8740
|
+
const hints = buildGraphHints(dir, graph);
|
|
8741
|
+
const knowledgeByPath = /* @__PURE__ */ new Map();
|
|
8742
|
+
for (const a of discoverKnowledge(dir).filter((a2) => !a2.isWorkItem)) {
|
|
8743
|
+
const id = a.id || a.relPath.replace(/[/\\]/g, "-").replace(/\.md$/, "");
|
|
8744
|
+
const layer = a.layer === "module" ? "tech" : a.layer;
|
|
8745
|
+
knowledgeByPath.set(a.relPath, { id, layer });
|
|
8746
|
+
}
|
|
8747
|
+
const wiModules = /* @__PURE__ */ new Map();
|
|
8748
|
+
for (const wi of discoverWorkItems(dir)) wiModules.set(wi.id || wi.title, wi.affectedModules);
|
|
8749
|
+
const mapped = loadMappedModules(dir);
|
|
8750
|
+
const groupIds = /* @__PURE__ */ new Set(["core", ...mapped.map((m) => m.id)]);
|
|
8751
|
+
const nodes = graph.nodes.map((n) => toNode(n, knowledgeByPath, wiModules, groupIds));
|
|
8752
|
+
const relationships = graph.edges.map((e) => ({
|
|
8753
|
+
id: `${e.from}~${e.type}~${e.to}`,
|
|
8754
|
+
source: e.from,
|
|
8755
|
+
target: e.to,
|
|
8756
|
+
type: e.type,
|
|
8757
|
+
label: EDGE_LABELS[e.type] ?? e.type.replace(/_/g, " ")
|
|
8758
|
+
}));
|
|
8759
|
+
const usedGroups = new Set(nodes.map((n) => n.moduleId).filter(Boolean));
|
|
8760
|
+
const allGroups = [
|
|
8761
|
+
{ id: "core", label: "core", repositoryId: "core", available: true },
|
|
8762
|
+
// A mapped module is available when its repository path resolves relative to the project.
|
|
8763
|
+
...mapped.map((m) => ({ id: m.id, label: m.id, repositoryId: m.id, available: m.repoPath ? exists(join(dir, m.repoPath)) : false }))
|
|
8764
|
+
];
|
|
8765
|
+
const groups = allGroups.filter((g) => usedGroups.has(g.id) || g.available === false);
|
|
8766
|
+
return {
|
|
8767
|
+
system: { name: config.project.name },
|
|
8768
|
+
nodes,
|
|
8769
|
+
relationships,
|
|
8770
|
+
groups,
|
|
8771
|
+
metadata: {
|
|
8772
|
+
projectName: config.project.name,
|
|
8773
|
+
structure: config.project.structure,
|
|
8774
|
+
nodeCount: nodes.length,
|
|
8775
|
+
relationshipCount: relationships.length,
|
|
8776
|
+
coverage: hints.quality,
|
|
8777
|
+
available: nodes.length > 0
|
|
8458
8778
|
}
|
|
8779
|
+
};
|
|
8780
|
+
}
|
|
8781
|
+
function toNode(n, knowledgeByPath, wiModules, groupIds) {
|
|
8782
|
+
const node = { id: n.id, type: n.type, label: n.label };
|
|
8783
|
+
if (n.status) node.status = n.status;
|
|
8784
|
+
if (n.path && !/^([a-zA-Z]:[\\/]|\/)/.test(n.path)) node.path = n.path;
|
|
8785
|
+
if (n.type === "work-item") {
|
|
8786
|
+
const wiId = n.id.startsWith("wi:") ? n.id.slice(3) : n.id;
|
|
8787
|
+
node.workItemRef = wiId;
|
|
8788
|
+
const affected = (wiModules.get(wiId) ?? []).filter((m) => groupIds.has(m));
|
|
8789
|
+
if (affected.length === 1) node.moduleId = affected[0];
|
|
8459
8790
|
}
|
|
8460
|
-
|
|
8461
|
-
|
|
8791
|
+
if (n.path) {
|
|
8792
|
+
const ref = knowledgeByPath.get(n.path);
|
|
8793
|
+
if (ref) node.knowledgeRef = ref;
|
|
8462
8794
|
}
|
|
8463
|
-
|
|
8464
|
-
if (input.acceptanceCriteria.length === 0) findings.push({ level: "warning", message: "No acceptance criteria have been defined." });
|
|
8465
|
-
if (input.scopeConfidence?.level === "low") findings.push({ level: "warning", message: "Scope confidence is Low." });
|
|
8466
|
-
if (!input.scopeConfidence) findings.push({ level: "warning", message: "Scope confidence has not been assessed." });
|
|
8467
|
-
for (const s of input.impactAnalysis) if (s.status === "unknown") findings.push({ level: "fyi", message: `Impact on ${s.surface} is unknown.` });
|
|
8468
|
-
return findings;
|
|
8469
|
-
}
|
|
8470
|
-
function applyRefinement(dir, workItemId, proposal, expectedRevision) {
|
|
8471
|
-
const { input } = normalizeAndValidateProposal(dir, workItemId, proposal);
|
|
8472
|
-
return updateWorkItem(dir, workItemId, input, expectedRevision);
|
|
8795
|
+
return node;
|
|
8473
8796
|
}
|
|
8474
8797
|
export {
|
|
8475
8798
|
WorkItemNotFoundError,
|
|
8476
8799
|
WorkItemWriteError,
|
|
8477
8800
|
analyzeCrossRepoEvidence,
|
|
8478
8801
|
analyzeScopeCoverage,
|
|
8479
|
-
applyRefinement,
|
|
8480
|
-
assembleRefinementContext,
|
|
8481
8802
|
buildProjectExplanation,
|
|
8482
8803
|
buildProjectRoute,
|
|
8483
8804
|
buildReadinessReport,
|
|
8805
|
+
buildRefinementHandoff,
|
|
8806
|
+
computeRefinementStatus,
|
|
8484
8807
|
createWorkItem,
|
|
8485
8808
|
cwd,
|
|
8486
8809
|
discoverKnowledge,
|
|
8487
8810
|
discoverWorkItems,
|
|
8488
8811
|
exists,
|
|
8812
|
+
getSystemMapProjection,
|
|
8489
8813
|
getWorkItem,
|
|
8490
|
-
getWorkItemAgentAssets,
|
|
8491
8814
|
getWorkItemCaptureDefinition,
|
|
8492
8815
|
getWorkItemForEdit,
|
|
8493
8816
|
getWorkItems,
|
|
@@ -8500,7 +8823,6 @@ export {
|
|
|
8500
8823
|
lifecycleStateOf,
|
|
8501
8824
|
loadConfig,
|
|
8502
8825
|
loadMappedModules,
|
|
8503
|
-
normalizeAndValidateProposal,
|
|
8504
8826
|
readFile,
|
|
8505
8827
|
transitionWorkItem,
|
|
8506
8828
|
updateWorkItem,
|