@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/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 KNOWLEDGE2 = "knowledge";
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(`/${KNOWLEDGE2}/business/`)) return "Business";
1754
- if (p2.includes(`/${KNOWLEDGE2}/product/`)) return "Product";
1755
- if (p2.includes(`/${KNOWLEDGE2}/tech/`)) return "Tech";
1756
- if (p2.includes(`/${KNOWLEDGE2}/delivery/`)) return "Delivery";
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, KNOWLEDGE2);
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, KNOWLEDGE2, "tech", "decisions"))) acc.Tech.structured = true;
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
- return {
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
- function getWorkItemAgentAssets() {
8358
- const agent = AGENT_PROMPTS.find((p2) => p2.fileName === "work-item-agent.md");
8359
- const skill2 = skillById("work-item-refinement");
8360
- return { agentPrompt: agent?.content ?? "", skill: skill2?.content ?? null };
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 knowledgeArtifacts = discoverKnowledge(dir).filter(
8366
- (a) => !a.isWorkItem && a.type !== "skill" && a.type !== "agent" && a.layer !== "unknown"
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
- workItem: { id: edit.id, title: edit.title, status: edit.status, intent: edit.summary ?? edit.title, current: stripEdit(edit) },
8373
- project: { name: config?.project.name ?? "unknown", state: config?.project.state ?? "unknown", structure: config?.project.structure ?? "unknown" },
8374
- modules,
8375
- knowledge,
8376
- decisions,
8377
- revision: edit.revision
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
- function stripEdit(edit) {
8381
- const { id: _i, status: _s, revision: _r, path: _p, editable: _e, editableReason: _er, ...input } = edit;
8382
- return input;
8383
- }
8384
- var VALID_COVERAGE2 = /* @__PURE__ */ new Set(["affected", "reviewed-not-affected", "unknown", "not-applicable"]);
8385
- var VALID_CONFIDENCE2 = /* @__PURE__ */ new Set(["high", "medium", "low"]);
8386
- function str(v) {
8387
- return typeof v === "string" && v.trim() ? v.trim() : void 0;
8388
- }
8389
- function strList(v) {
8390
- return Array.isArray(v) ? v.map((x) => typeof x === "string" ? x.trim() : "").filter(Boolean) : [];
8391
- }
8392
- function normalizeAndValidateProposal(dir, workItemId, proposal) {
8393
- const ctx = assembleRefinementContext(dir, workItemId);
8394
- const knownModules = new Set(ctx.modules);
8395
- const knownDecisions = new Set(ctx.decisions.map((d) => d.id));
8396
- const knownKnowledge = new Set(ctx.knowledge.map((k) => k.id));
8397
- const extraFindings = [];
8398
- const input = { ...ctx.workItem.current };
8399
- if (str(proposal.title)) input.title = str(proposal.title);
8400
- const o = proposal.outcome ?? {};
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 evaluate(input, knownModules) {
8454
- const findings = [];
8455
- for (const c of input.moduleCoverage) {
8456
- if (c.status === "affected" && !input.affectedModules.includes(c.id)) {
8457
- findings.push({ level: "blocking", message: `${c.id} is marked affected in module coverage but is missing from affected_modules.` });
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
- for (const m of input.affectedModules) {
8461
- if (!knownModules.has(m)) findings.push({ level: "blocking", message: `Module "${m}" is not registered in this project.` });
8791
+ if (n.path) {
8792
+ const ref = knowledgeByPath.get(n.path);
8793
+ if (ref) node.knowledgeRef = ref;
8462
8794
  }
8463
- if (!input.targetBehavior?.trim()) findings.push({ level: "warning", message: "Target behavior is not defined." });
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/cli",
3
- "version": "3.74.1",
3
+ "version": "3.76.0",
4
4
  "description": "Knowledge Driven Development toolkit",
5
5
  "license": "MIT",
6
6
  "repository": {