@kaddo/cli 3.77.0 → 3.78.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.
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>admin</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-Y7S2WW1U.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/assets/index-BPQdYfAp.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
@@ -69,6 +69,7 @@ import {
|
|
|
69
69
|
getWorkItemCaptureDefinition as coreGetCaptureDefinition,
|
|
70
70
|
buildRefinementHandoff as coreBuildRefinementHandoff,
|
|
71
71
|
getSystemMapProjection as coreGetSystemMapProjection,
|
|
72
|
+
buildTopologyEnrichmentHandoff as coreBuildTopologyHandoff,
|
|
72
73
|
WorkItemWriteError,
|
|
73
74
|
exists,
|
|
74
75
|
join,
|
|
@@ -139,6 +140,11 @@ function getCaptureDefinition() {
|
|
|
139
140
|
function getSystemMap(dir) {
|
|
140
141
|
return coreGetSystemMapProjection(dir);
|
|
141
142
|
}
|
|
143
|
+
function getTopologyHandoff(dir) {
|
|
144
|
+
const config = loadConfig(dir);
|
|
145
|
+
if (!config) throw new CoreError("PROJECT_NOT_FOUND", "No Kaddo project was found.");
|
|
146
|
+
return coreBuildTopologyHandoff(dir, config.project.name ?? "this project");
|
|
147
|
+
}
|
|
142
148
|
function getRefinementHandoff(dir, workItemId) {
|
|
143
149
|
assertValidWorkItemId(workItemId);
|
|
144
150
|
try {
|
|
@@ -590,7 +596,12 @@ var SystemMapNodeSchema = z.object({
|
|
|
590
596
|
path: z.string().optional(),
|
|
591
597
|
workItemRef: z.string().optional(),
|
|
592
598
|
knowledgeRef: z.object({ id: z.string(), layer: z.string() }).optional(),
|
|
593
|
-
moduleId: z.string().optional()
|
|
599
|
+
moduleId: z.string().optional(),
|
|
600
|
+
purpose: z.string().optional(),
|
|
601
|
+
implementationRefs: z.array(z.string()).optional(),
|
|
602
|
+
knowledgeRefs: z.array(z.object({ id: z.string(), layer: z.string() })).optional(),
|
|
603
|
+
provenance: z.string().optional(),
|
|
604
|
+
evidence: z.array(z.string()).optional()
|
|
594
605
|
});
|
|
595
606
|
var SystemMapRelationshipSchema = z.object({
|
|
596
607
|
id: z.string(),
|
|
@@ -618,9 +629,20 @@ var SystemMapProjectionSchema = z.object({
|
|
|
618
629
|
coverage: z.enum(["good", "partial", "sparse", "empty"]),
|
|
619
630
|
available: z.boolean(),
|
|
620
631
|
dimensions: z.object({ system: z.number(), knowledge: z.number(), delivery: z.number(), implementation: z.number(), unknown: z.number() }),
|
|
621
|
-
topologyAvailable: z.boolean()
|
|
632
|
+
topologyAvailable: z.boolean(),
|
|
633
|
+
topologyStatus: z.enum(["unavailable", "partial", "available"]),
|
|
634
|
+
semanticEntityCount: z.number(),
|
|
635
|
+
technicalRelationshipCount: z.number(),
|
|
636
|
+
topologyFindings: z.array(z.object({ level: z.enum(["blocking", "warning"]), message: z.string() }))
|
|
622
637
|
})
|
|
623
638
|
});
|
|
639
|
+
var TopologyEnrichmentHandoffSchema = z.object({
|
|
640
|
+
projectName: z.string(),
|
|
641
|
+
recommendedAgent: z.string(),
|
|
642
|
+
recommendedSkill: z.string(),
|
|
643
|
+
targetFile: z.string(),
|
|
644
|
+
text: z.string()
|
|
645
|
+
});
|
|
624
646
|
var ErrorResponseSchema = z.object({
|
|
625
647
|
error: z.object({
|
|
626
648
|
code: z.string(),
|
|
@@ -782,6 +804,7 @@ async function createAdminServer(opts) {
|
|
|
782
804
|
app.get("/api/v1/admin/route", coreRoute(getProjectRoute));
|
|
783
805
|
app.get("/api/v1/admin/findings", coreRoute(getFindings));
|
|
784
806
|
app.get("/api/v1/admin/system", coreRoute(getSystemMap));
|
|
807
|
+
app.get("/api/v1/admin/system/topology-handoff", coreRoute(getTopologyHandoff));
|
|
785
808
|
app.get("/api/v1/admin/knowledge/inventory", coreRoute(getKnowledgeInventory));
|
|
786
809
|
app.get("/api/v1/admin/knowledge/artifact/:artifactId", async (request) => {
|
|
787
810
|
try {
|
|
@@ -940,6 +963,7 @@ export {
|
|
|
940
963
|
SystemMapNodeSchema,
|
|
941
964
|
SystemMapProjectionSchema,
|
|
942
965
|
SystemMapRelationshipSchema,
|
|
966
|
+
TopologyEnrichmentHandoffSchema,
|
|
943
967
|
ValidationResultSchema,
|
|
944
968
|
WorkItemCreateSchema,
|
|
945
969
|
WorkItemCreateWithAnswersSchema,
|
package/dist/core.js
CHANGED
|
@@ -8712,6 +8712,159 @@ function buildRefinementHandoff(dir, workItemId) {
|
|
|
8712
8712
|
};
|
|
8713
8713
|
}
|
|
8714
8714
|
|
|
8715
|
+
// src/core/system-topology.ts
|
|
8716
|
+
import { parse as parseYaml8 } from "yaml";
|
|
8717
|
+
var TOPOLOGY_FILE = "knowledge/tech/system-topology.yml";
|
|
8718
|
+
var SYSTEM_ENTITY_KINDS = /* @__PURE__ */ new Set([
|
|
8719
|
+
"system",
|
|
8720
|
+
"application",
|
|
8721
|
+
"service",
|
|
8722
|
+
"component",
|
|
8723
|
+
"api",
|
|
8724
|
+
"interface",
|
|
8725
|
+
"datastore",
|
|
8726
|
+
"queue",
|
|
8727
|
+
"job",
|
|
8728
|
+
"external-system",
|
|
8729
|
+
"module",
|
|
8730
|
+
"unknown"
|
|
8731
|
+
]);
|
|
8732
|
+
var TECHNICAL_RELATIONSHIP_TYPES = /* @__PURE__ */ new Set([
|
|
8733
|
+
"contains",
|
|
8734
|
+
"depends-on",
|
|
8735
|
+
"calls",
|
|
8736
|
+
"reads-from",
|
|
8737
|
+
"writes-to",
|
|
8738
|
+
"publishes-to",
|
|
8739
|
+
"subscribes-to",
|
|
8740
|
+
"integrates-with",
|
|
8741
|
+
"runs-on",
|
|
8742
|
+
"implemented-by"
|
|
8743
|
+
]);
|
|
8744
|
+
var PROVENANCE = /* @__PURE__ */ new Set(["declared", "derived", "agent-reviewed"]);
|
|
8745
|
+
function isRelative(p2) {
|
|
8746
|
+
return typeof p2 === "string" && p2.trim() !== "" && !/^([a-zA-Z]:[\\/]|\/)/.test(p2) && !p2.includes("..");
|
|
8747
|
+
}
|
|
8748
|
+
function strList(v) {
|
|
8749
|
+
return Array.isArray(v) ? v.map((x) => typeof x === "string" ? x.trim() : "").filter(Boolean) : [];
|
|
8750
|
+
}
|
|
8751
|
+
function loadSystemTopology(dir) {
|
|
8752
|
+
const path3 = join(dir, TOPOLOGY_FILE);
|
|
8753
|
+
if (!exists(path3)) return { entities: [], relationships: [], findings: [], declared: false };
|
|
8754
|
+
let parsed;
|
|
8755
|
+
try {
|
|
8756
|
+
parsed = parseYaml8(readFile(path3)) ?? {};
|
|
8757
|
+
} catch {
|
|
8758
|
+
return { entities: [], relationships: [], findings: [{ level: "blocking", message: "system-topology.yml could not be parsed." }], declared: true };
|
|
8759
|
+
}
|
|
8760
|
+
const findings = [];
|
|
8761
|
+
const validModules = /* @__PURE__ */ new Set(["core", ...loadMappedModules(dir).map((m) => m.id)]);
|
|
8762
|
+
const rawEntities = Array.isArray(parsed.entities) ? parsed.entities : [];
|
|
8763
|
+
const entities = [];
|
|
8764
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
8765
|
+
for (const raw of rawEntities) {
|
|
8766
|
+
if (!raw || typeof raw !== "object") continue;
|
|
8767
|
+
const e = raw;
|
|
8768
|
+
const id = typeof e.id === "string" ? e.id.trim() : "";
|
|
8769
|
+
if (!id) {
|
|
8770
|
+
findings.push({ level: "warning", message: "A topology entity is missing an id and was skipped." });
|
|
8771
|
+
continue;
|
|
8772
|
+
}
|
|
8773
|
+
if (seenIds.has(id)) {
|
|
8774
|
+
findings.push({ level: "blocking", message: `Duplicate topology entity id "${id}".` });
|
|
8775
|
+
continue;
|
|
8776
|
+
}
|
|
8777
|
+
const kind = typeof e.kind === "string" ? e.kind.trim() : "unknown";
|
|
8778
|
+
if (!SYSTEM_ENTITY_KINDS.has(kind)) {
|
|
8779
|
+
findings.push({ level: "warning", message: `Entity "${id}" has an unknown kind "${kind}"; treated as unknown.` });
|
|
8780
|
+
}
|
|
8781
|
+
const moduleRaw = typeof e.module === "string" ? e.module.trim() : void 0;
|
|
8782
|
+
if (moduleRaw && !validModules.has(moduleRaw)) findings.push({ level: "warning", message: `Entity "${id}" references unregistered module "${moduleRaw}".` });
|
|
8783
|
+
const implementation = strList(e.implementation).filter((p2) => {
|
|
8784
|
+
if (isRelative(p2)) return true;
|
|
8785
|
+
findings.push({ level: "warning", message: `Entity "${id}" implementation ref "${p2}" is not a safe relative path and was dropped.` });
|
|
8786
|
+
return false;
|
|
8787
|
+
});
|
|
8788
|
+
const provenance = typeof e.provenance === "string" && PROVENANCE.has(e.provenance) ? e.provenance : void 0;
|
|
8789
|
+
seenIds.add(id);
|
|
8790
|
+
entities.push({
|
|
8791
|
+
id,
|
|
8792
|
+
kind: SYSTEM_ENTITY_KINDS.has(kind) ? kind : "unknown",
|
|
8793
|
+
label: typeof e.label === "string" && e.label.trim() ? e.label.trim() : id,
|
|
8794
|
+
...typeof e.purpose === "string" && e.purpose.trim() ? { purpose: e.purpose.trim() } : {},
|
|
8795
|
+
...moduleRaw && validModules.has(moduleRaw) ? { moduleId: moduleRaw } : {},
|
|
8796
|
+
implementationRefs: implementation,
|
|
8797
|
+
knowledgeRefs: strList(e.knowledge),
|
|
8798
|
+
...provenance ? { provenance } : {},
|
|
8799
|
+
evidence: strList(e.evidence).filter(isRelative)
|
|
8800
|
+
});
|
|
8801
|
+
}
|
|
8802
|
+
const entityIds = new Set(entities.map((e) => e.id));
|
|
8803
|
+
const rawRels = Array.isArray(parsed.relationships) ? parsed.relationships : [];
|
|
8804
|
+
const relationships = [];
|
|
8805
|
+
for (const raw of rawRels) {
|
|
8806
|
+
if (!raw || typeof raw !== "object") continue;
|
|
8807
|
+
const r = raw;
|
|
8808
|
+
const from = typeof r.from === "string" ? r.from.trim() : "";
|
|
8809
|
+
const to = typeof r.to === "string" ? r.to.trim() : "";
|
|
8810
|
+
const type = typeof r.type === "string" ? r.type.trim() : "";
|
|
8811
|
+
if (!from || !to || !type) {
|
|
8812
|
+
findings.push({ level: "warning", message: "A topology relationship is missing from/to/type and was skipped." });
|
|
8813
|
+
continue;
|
|
8814
|
+
}
|
|
8815
|
+
if (!TECHNICAL_RELATIONSHIP_TYPES.has(type)) {
|
|
8816
|
+
findings.push({ level: "warning", message: `Relationship type "${type}" is not recognized and was skipped.` });
|
|
8817
|
+
continue;
|
|
8818
|
+
}
|
|
8819
|
+
if (!entityIds.has(from) || !entityIds.has(to)) {
|
|
8820
|
+
findings.push({ level: "blocking", message: `Relationship ${from} \u2192 ${to} references an unknown entity endpoint.` });
|
|
8821
|
+
continue;
|
|
8822
|
+
}
|
|
8823
|
+
relationships.push({ from, to, type, evidence: strList(r.evidence).filter(isRelative) });
|
|
8824
|
+
}
|
|
8825
|
+
return { entities, relationships, findings, declared: true };
|
|
8826
|
+
}
|
|
8827
|
+
function validateSystemTopology(dir) {
|
|
8828
|
+
return loadSystemTopology(dir).findings;
|
|
8829
|
+
}
|
|
8830
|
+
function buildTopologyEnrichmentHandoff(dir, projectName) {
|
|
8831
|
+
const mappedModules = loadMappedModules(dir).map((m) => m.id);
|
|
8832
|
+
const lines = [
|
|
8833
|
+
`Enrich the semantic system topology for the Kaddo project "${projectName}".`,
|
|
8834
|
+
"",
|
|
8835
|
+
"Use the canonical architecture-agent and graph-metadata-review skill (via Kaddo MCP or skills).",
|
|
8836
|
+
"Inspect the actual repository and relevant mapped modules \u2014 do not infer architecture from",
|
|
8837
|
+
"filenames or directory names."
|
|
8838
|
+
];
|
|
8839
|
+
if (mappedModules.length > 0) {
|
|
8840
|
+
lines.push("", `This is a multirepo project. Inspect the relevant mapped modules (${mappedModules.join(", ")}) before finalizing the topology.`);
|
|
8841
|
+
}
|
|
8842
|
+
lines.push(
|
|
8843
|
+
"",
|
|
8844
|
+
`Write the result to ${TOPOLOGY_FILE} as declared entities and relationships. For each entity`,
|
|
8845
|
+
"capture, only when supported by evidence:",
|
|
8846
|
+
"- a stable id and semantic kind (application/service/component/api/interface/datastore/queue/job/external-system);",
|
|
8847
|
+
"- a responsibility/purpose (what it does, not how);",
|
|
8848
|
+
"- the owning module/repository;",
|
|
8849
|
+
"- implementation references (relative paths);",
|
|
8850
|
+
"- relevant Knowledge references (capability/ADR ids), not Kaddo operational assets.",
|
|
8851
|
+
"",
|
|
8852
|
+
"Capture evidence-backed technical relationships: contains, calls, depends-on, reads-from,",
|
|
8853
|
+
"writes-to, integrates-with, runs-on. Preserve unknowns when evidence is insufficient.",
|
|
8854
|
+
"",
|
|
8855
|
+
"Review the proposed graph metadata (duplicate ids, dangling relationships, unknown modules,",
|
|
8856
|
+
"invalid references) before writing.",
|
|
8857
|
+
"Do not implement application changes. Do not run mutating Git operations."
|
|
8858
|
+
);
|
|
8859
|
+
return {
|
|
8860
|
+
projectName,
|
|
8861
|
+
recommendedAgent: "architecture-agent",
|
|
8862
|
+
recommendedSkill: "graph-metadata-review",
|
|
8863
|
+
targetFile: TOPOLOGY_FILE,
|
|
8864
|
+
text: lines.join("\n")
|
|
8865
|
+
};
|
|
8866
|
+
}
|
|
8867
|
+
|
|
8715
8868
|
// src/core/system-map.ts
|
|
8716
8869
|
function dimensionOf(type) {
|
|
8717
8870
|
switch (type) {
|
|
@@ -8770,7 +8923,20 @@ function emptyProjection(name, structure) {
|
|
|
8770
8923
|
nodes: [],
|
|
8771
8924
|
relationships: [],
|
|
8772
8925
|
groups: [],
|
|
8773
|
-
metadata: {
|
|
8926
|
+
metadata: {
|
|
8927
|
+
projectName: name,
|
|
8928
|
+
structure,
|
|
8929
|
+
nodeCount: 0,
|
|
8930
|
+
relationshipCount: 0,
|
|
8931
|
+
coverage: "empty",
|
|
8932
|
+
available: false,
|
|
8933
|
+
dimensions: emptyDimensions(),
|
|
8934
|
+
topologyAvailable: false,
|
|
8935
|
+
topologyStatus: "unavailable",
|
|
8936
|
+
semanticEntityCount: 0,
|
|
8937
|
+
technicalRelationshipCount: 0,
|
|
8938
|
+
topologyFindings: []
|
|
8939
|
+
}
|
|
8774
8940
|
};
|
|
8775
8941
|
}
|
|
8776
8942
|
function getSystemMapProjection(dir) {
|
|
@@ -8796,6 +8962,38 @@ function getSystemMapProjection(dir) {
|
|
|
8796
8962
|
type: e.type,
|
|
8797
8963
|
label: EDGE_LABELS[e.type] ?? e.type.replace(/_/g, " ")
|
|
8798
8964
|
}));
|
|
8965
|
+
const knowledgeById = /* @__PURE__ */ new Map();
|
|
8966
|
+
for (const ref of knowledgeByPath.values()) knowledgeById.set(ref.id, ref);
|
|
8967
|
+
const topology = loadSystemTopology(dir);
|
|
8968
|
+
const existingIds = new Set(nodes.map((n) => n.id));
|
|
8969
|
+
for (const e of topology.entities) {
|
|
8970
|
+
const nodeId = `sys:${e.id}`;
|
|
8971
|
+
const node = {
|
|
8972
|
+
id: nodeId,
|
|
8973
|
+
type: e.kind,
|
|
8974
|
+
label: e.label,
|
|
8975
|
+
dimension: "system",
|
|
8976
|
+
implementationRefs: e.implementationRefs,
|
|
8977
|
+
knowledgeRefs: e.knowledgeRefs.map((k) => knowledgeById.get(k)).filter(Boolean),
|
|
8978
|
+
evidence: e.evidence
|
|
8979
|
+
};
|
|
8980
|
+
if (e.purpose) node.purpose = e.purpose;
|
|
8981
|
+
if (e.moduleId) node.moduleId = e.moduleId;
|
|
8982
|
+
if (e.provenance) node.provenance = e.provenance;
|
|
8983
|
+
nodes.push(node);
|
|
8984
|
+
existingIds.add(nodeId);
|
|
8985
|
+
for (const ref of e.implementationRefs) {
|
|
8986
|
+
const fileId = `file:${ref}`;
|
|
8987
|
+
if (!existingIds.has(fileId)) {
|
|
8988
|
+
nodes.push({ id: fileId, type: "file", label: ref.split("/").pop() || ref, dimension: "implementation", path: ref });
|
|
8989
|
+
existingIds.add(fileId);
|
|
8990
|
+
}
|
|
8991
|
+
relationships.push({ id: `${nodeId}~implemented-by~${fileId}`, source: nodeId, target: fileId, type: "implemented-by", label: "implemented by" });
|
|
8992
|
+
}
|
|
8993
|
+
}
|
|
8994
|
+
for (const r of topology.relationships) {
|
|
8995
|
+
relationships.push({ id: `sys:${r.from}~${r.type}~sys:${r.to}`, source: `sys:${r.from}`, target: `sys:${r.to}`, type: r.type, label: r.type.replace(/-/g, " ") });
|
|
8996
|
+
}
|
|
8799
8997
|
const usedGroups = new Set(nodes.map((n) => n.moduleId).filter(Boolean));
|
|
8800
8998
|
const allGroups = [
|
|
8801
8999
|
{ id: "core", label: "core", repositoryId: "core", available: true },
|
|
@@ -8805,6 +9003,9 @@ function getSystemMapProjection(dir) {
|
|
|
8805
9003
|
const groups = allGroups.filter((g) => usedGroups.has(g.id) || g.available === false);
|
|
8806
9004
|
const dimensions = emptyDimensions();
|
|
8807
9005
|
for (const n of nodes) dimensions[n.dimension]++;
|
|
9006
|
+
const semanticEntityCount = topology.entities.length;
|
|
9007
|
+
const technicalRelationshipCount = relationships.filter((r) => TECHNICAL_RELATIONSHIP_TYPES.has(r.type)).length;
|
|
9008
|
+
const topologyStatus = semanticEntityCount === 0 ? "unavailable" : topology.relationships.length > 0 ? "available" : "partial";
|
|
8808
9009
|
return {
|
|
8809
9010
|
system: { name: config.project.name },
|
|
8810
9011
|
nodes,
|
|
@@ -8818,7 +9019,11 @@ function getSystemMapProjection(dir) {
|
|
|
8818
9019
|
coverage: hints.quality,
|
|
8819
9020
|
available: nodes.length > 0,
|
|
8820
9021
|
dimensions,
|
|
8821
|
-
topologyAvailable: dimensions.system > 0
|
|
9022
|
+
topologyAvailable: dimensions.system > 0,
|
|
9023
|
+
topologyStatus,
|
|
9024
|
+
semanticEntityCount,
|
|
9025
|
+
technicalRelationshipCount,
|
|
9026
|
+
topologyFindings: topology.findings
|
|
8822
9027
|
}
|
|
8823
9028
|
};
|
|
8824
9029
|
}
|
|
@@ -8858,6 +9063,7 @@ function toNode(n, knowledgeByPath, wiModules, groupIds) {
|
|
|
8858
9063
|
return node;
|
|
8859
9064
|
}
|
|
8860
9065
|
export {
|
|
9066
|
+
TOPOLOGY_FILE,
|
|
8861
9067
|
WorkItemNotFoundError,
|
|
8862
9068
|
WorkItemWriteError,
|
|
8863
9069
|
analyzeCrossRepoEvidence,
|
|
@@ -8866,6 +9072,7 @@ export {
|
|
|
8866
9072
|
buildProjectRoute,
|
|
8867
9073
|
buildReadinessReport,
|
|
8868
9074
|
buildRefinementHandoff,
|
|
9075
|
+
buildTopologyEnrichmentHandoff,
|
|
8869
9076
|
computeRefinementStatus,
|
|
8870
9077
|
createWorkItem,
|
|
8871
9078
|
cwd,
|
|
@@ -8887,8 +9094,10 @@ export {
|
|
|
8887
9094
|
lifecycleStateOf,
|
|
8888
9095
|
loadConfig,
|
|
8889
9096
|
loadMappedModules,
|
|
9097
|
+
loadSystemTopology,
|
|
8890
9098
|
readFile,
|
|
8891
9099
|
transitionWorkItem,
|
|
8892
9100
|
updateWorkItem,
|
|
9101
|
+
validateSystemTopology,
|
|
8893
9102
|
validateWorkItem
|
|
8894
9103
|
};
|