@kaddo/cli 3.78.0 → 3.79.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
|
@@ -116,8 +116,8 @@ function loadConfig(dir) {
|
|
|
116
116
|
const parsed = configSchema.safeParse(raw ?? {});
|
|
117
117
|
if (!parsed.success) {
|
|
118
118
|
const issues = parsed.error.issues.map((i) => {
|
|
119
|
-
const
|
|
120
|
-
return
|
|
119
|
+
const path4 = i.path.join(".");
|
|
120
|
+
return path4 ? ` - ${path4}: ${i.message}` : ` - ${i.message}`;
|
|
121
121
|
}).join("\n");
|
|
122
122
|
throw new ConfigError(`Invalid .kaddo/config.yml:
|
|
123
123
|
${issues}`);
|
|
@@ -352,11 +352,11 @@ function moduleArtifactCoverage(dir, id) {
|
|
|
352
352
|
};
|
|
353
353
|
}
|
|
354
354
|
function loadMappedModules(dir) {
|
|
355
|
-
const
|
|
356
|
-
if (!exists(
|
|
355
|
+
const path4 = join(dir, DESCRIPTOR_PATH);
|
|
356
|
+
if (!exists(path4)) return [];
|
|
357
357
|
let parsed;
|
|
358
358
|
try {
|
|
359
|
-
parsed = parseYaml3(readFile(
|
|
359
|
+
parsed = parseYaml3(readFile(path4));
|
|
360
360
|
} catch {
|
|
361
361
|
return [];
|
|
362
362
|
}
|
|
@@ -1590,7 +1590,7 @@ function sectionParagraph(md, title) {
|
|
|
1590
1590
|
}
|
|
1591
1591
|
return "";
|
|
1592
1592
|
}
|
|
1593
|
-
function parseCapsule(id,
|
|
1593
|
+
function parseCapsule(id, path4, md) {
|
|
1594
1594
|
const { data } = matter2(md);
|
|
1595
1595
|
const updatedAt = data.updated_at ? String(data.updated_at) : void 0;
|
|
1596
1596
|
let ageDays = null;
|
|
@@ -1600,7 +1600,7 @@ function parseCapsule(id, path3, md) {
|
|
|
1600
1600
|
}
|
|
1601
1601
|
return {
|
|
1602
1602
|
id,
|
|
1603
|
-
path:
|
|
1603
|
+
path: path4,
|
|
1604
1604
|
system: data.system ? String(data.system) : id,
|
|
1605
1605
|
owner: data.owner ? String(data.owner) : void 0,
|
|
1606
1606
|
updatedAt,
|
|
@@ -1662,9 +1662,9 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
1662
1662
|
];
|
|
1663
1663
|
const presentLayers = [];
|
|
1664
1664
|
for (const layer of layerDocs) {
|
|
1665
|
-
const
|
|
1666
|
-
if (
|
|
1667
|
-
addNode({ id: layer.id, type: layer.type, label: layer.label, path:
|
|
1665
|
+
const path4 = layer.files.map((f) => `${KNOWLEDGE2}/${f}`).find((rel) => exists(join(dir, rel)));
|
|
1666
|
+
if (path4) {
|
|
1667
|
+
addNode({ id: layer.id, type: layer.type, label: layer.label, path: path4 });
|
|
1668
1668
|
presentLayers.push(layer.id);
|
|
1669
1669
|
}
|
|
1670
1670
|
}
|
|
@@ -3060,10 +3060,10 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
|
|
|
3060
3060
|
const q = (rel) => analyzeKnowledgeArtifact(dir, rel);
|
|
3061
3061
|
const resolveAgent = (agent) => {
|
|
3062
3062
|
const file = agent.endsWith(".md") ? agent : `${agent}.md`;
|
|
3063
|
-
const
|
|
3064
|
-
const installed = isFile(join(dir,
|
|
3063
|
+
const path4 = agentInstallPath(file);
|
|
3064
|
+
const installed = isFile(join(dir, path4));
|
|
3065
3065
|
const group = agentGroupOf(file);
|
|
3066
|
-
return { agentPath:
|
|
3066
|
+
return { agentPath: path4, agentInstalled: installed, installCommand: installed ? void 0 : `kaddo add agents --group ${group}` };
|
|
3067
3067
|
};
|
|
3068
3068
|
const moduleRepo = isModule(config);
|
|
3069
3069
|
const coreRepo = isCore(config);
|
|
@@ -8713,7 +8713,10 @@ function buildRefinementHandoff(dir, workItemId) {
|
|
|
8713
8713
|
}
|
|
8714
8714
|
|
|
8715
8715
|
// src/core/system-topology.ts
|
|
8716
|
-
import { parse as parseYaml8 } from "yaml";
|
|
8716
|
+
import { parse as parseYaml8, stringify as stringifyYaml4 } from "yaml";
|
|
8717
|
+
import fs3 from "fs";
|
|
8718
|
+
import path3 from "path";
|
|
8719
|
+
import crypto2 from "crypto";
|
|
8717
8720
|
var TOPOLOGY_FILE = "knowledge/tech/system-topology.yml";
|
|
8718
8721
|
var SYSTEM_ENTITY_KINDS = /* @__PURE__ */ new Set([
|
|
8719
8722
|
"system",
|
|
@@ -8749,22 +8752,27 @@ function strList(v) {
|
|
|
8749
8752
|
return Array.isArray(v) ? v.map((x) => typeof x === "string" ? x.trim() : "").filter(Boolean) : [];
|
|
8750
8753
|
}
|
|
8751
8754
|
function loadSystemTopology(dir) {
|
|
8752
|
-
const
|
|
8753
|
-
if (!exists(
|
|
8755
|
+
const filePath = join(dir, TOPOLOGY_FILE);
|
|
8756
|
+
if (!exists(filePath)) return { entities: [], relationships: [], findings: [], declared: false };
|
|
8757
|
+
const parsed = parseTopologyContent(readFile(filePath), dir);
|
|
8758
|
+
return { ...parsed, declared: true };
|
|
8759
|
+
}
|
|
8760
|
+
function parseTopologyContent(raw, dir) {
|
|
8754
8761
|
let parsed;
|
|
8755
8762
|
try {
|
|
8756
|
-
parsed = parseYaml8(
|
|
8763
|
+
parsed = parseYaml8(raw) ?? {};
|
|
8757
8764
|
} catch {
|
|
8758
|
-
return { entities: [], relationships: [], findings: [{ level: "blocking", message: "
|
|
8765
|
+
return { entities: [], relationships: [], findings: [{ level: "blocking", message: "The topology could not be parsed." }] };
|
|
8759
8766
|
}
|
|
8767
|
+
const sourceRevision = typeof parsed.source_revision === "string" ? parsed.source_revision : void 0;
|
|
8760
8768
|
const findings = [];
|
|
8761
8769
|
const validModules = /* @__PURE__ */ new Set(["core", ...loadMappedModules(dir).map((m) => m.id)]);
|
|
8762
8770
|
const rawEntities = Array.isArray(parsed.entities) ? parsed.entities : [];
|
|
8763
8771
|
const entities = [];
|
|
8764
8772
|
const seenIds = /* @__PURE__ */ new Set();
|
|
8765
|
-
for (const
|
|
8766
|
-
if (!
|
|
8767
|
-
const e =
|
|
8773
|
+
for (const raw2 of rawEntities) {
|
|
8774
|
+
if (!raw2 || typeof raw2 !== "object") continue;
|
|
8775
|
+
const e = raw2;
|
|
8768
8776
|
const id = typeof e.id === "string" ? e.id.trim() : "";
|
|
8769
8777
|
if (!id) {
|
|
8770
8778
|
findings.push({ level: "warning", message: "A topology entity is missing an id and was skipped." });
|
|
@@ -8780,12 +8788,16 @@ function loadSystemTopology(dir) {
|
|
|
8780
8788
|
}
|
|
8781
8789
|
const moduleRaw = typeof e.module === "string" ? e.module.trim() : void 0;
|
|
8782
8790
|
if (moduleRaw && !validModules.has(moduleRaw)) findings.push({ level: "warning", message: `Entity "${id}" references unregistered module "${moduleRaw}".` });
|
|
8783
|
-
const
|
|
8791
|
+
const rawImpl = e.implementation ?? e.implementation_refs;
|
|
8792
|
+
const implementation = strList(rawImpl).filter((p2) => {
|
|
8784
8793
|
if (isRelative(p2)) return true;
|
|
8785
8794
|
findings.push({ level: "warning", message: `Entity "${id}" implementation ref "${p2}" is not a safe relative path and was dropped.` });
|
|
8786
8795
|
return false;
|
|
8787
8796
|
});
|
|
8788
|
-
const
|
|
8797
|
+
const prov = e.provenance;
|
|
8798
|
+
const provOrigin = typeof prov === "string" ? prov : prov && typeof prov === "object" && typeof prov.origin === "string" ? String(prov.origin) : void 0;
|
|
8799
|
+
const provenance = provOrigin && PROVENANCE.has(provOrigin) ? provOrigin : void 0;
|
|
8800
|
+
const evidence = strList(e.evidence ?? (prov && typeof prov === "object" ? prov.evidence_refs : void 0)).filter(isRelative);
|
|
8789
8801
|
seenIds.add(id);
|
|
8790
8802
|
entities.push({
|
|
8791
8803
|
id,
|
|
@@ -8794,19 +8806,20 @@ function loadSystemTopology(dir) {
|
|
|
8794
8806
|
...typeof e.purpose === "string" && e.purpose.trim() ? { purpose: e.purpose.trim() } : {},
|
|
8795
8807
|
...moduleRaw && validModules.has(moduleRaw) ? { moduleId: moduleRaw } : {},
|
|
8796
8808
|
implementationRefs: implementation,
|
|
8797
|
-
knowledgeRefs: strList(e.knowledge),
|
|
8809
|
+
knowledgeRefs: strList(e.knowledge ?? e.knowledge_refs),
|
|
8798
8810
|
...provenance ? { provenance } : {},
|
|
8799
|
-
evidence
|
|
8811
|
+
evidence
|
|
8800
8812
|
});
|
|
8801
8813
|
}
|
|
8802
8814
|
const entityIds = new Set(entities.map((e) => e.id));
|
|
8803
8815
|
const rawRels = Array.isArray(parsed.relationships) ? parsed.relationships : [];
|
|
8804
8816
|
const relationships = [];
|
|
8805
|
-
|
|
8806
|
-
|
|
8807
|
-
|
|
8808
|
-
const
|
|
8809
|
-
const
|
|
8817
|
+
const seenRels = /* @__PURE__ */ new Set();
|
|
8818
|
+
for (const raw2 of rawRels) {
|
|
8819
|
+
if (!raw2 || typeof raw2 !== "object") continue;
|
|
8820
|
+
const r = raw2;
|
|
8821
|
+
const from = typeof r.from === "string" ? r.from.trim() : typeof r.source === "string" ? r.source.trim() : "";
|
|
8822
|
+
const to = typeof r.to === "string" ? r.to.trim() : typeof r.target === "string" ? r.target.trim() : "";
|
|
8810
8823
|
const type = typeof r.type === "string" ? r.type.trim() : "";
|
|
8811
8824
|
if (!from || !to || !type) {
|
|
8812
8825
|
findings.push({ level: "warning", message: "A topology relationship is missing from/to/type and was skipped." });
|
|
@@ -8820,13 +8833,101 @@ function loadSystemTopology(dir) {
|
|
|
8820
8833
|
findings.push({ level: "blocking", message: `Relationship ${from} \u2192 ${to} references an unknown entity endpoint.` });
|
|
8821
8834
|
continue;
|
|
8822
8835
|
}
|
|
8823
|
-
|
|
8836
|
+
const key = `${from}~${type}~${to}`;
|
|
8837
|
+
if (seenRels.has(key)) {
|
|
8838
|
+
findings.push({ level: "warning", message: `Duplicate relationship ${from} ${type} ${to} was skipped.` });
|
|
8839
|
+
continue;
|
|
8840
|
+
}
|
|
8841
|
+
seenRels.add(key);
|
|
8842
|
+
const prov = r.provenance;
|
|
8843
|
+
relationships.push({ from, to, type, evidence: strList(r.evidence ?? prov?.evidence_refs).filter(isRelative) });
|
|
8824
8844
|
}
|
|
8825
|
-
return { entities, relationships, findings,
|
|
8845
|
+
return { entities, relationships, findings, ...sourceRevision ? { sourceRevision } : {} };
|
|
8826
8846
|
}
|
|
8827
8847
|
function validateSystemTopology(dir) {
|
|
8828
8848
|
return loadSystemTopology(dir).findings;
|
|
8829
8849
|
}
|
|
8850
|
+
var TopologyWriteError = class extends Error {
|
|
8851
|
+
code;
|
|
8852
|
+
constructor(code, message) {
|
|
8853
|
+
super(message);
|
|
8854
|
+
this.name = "TopologyWriteError";
|
|
8855
|
+
this.code = code;
|
|
8856
|
+
}
|
|
8857
|
+
};
|
|
8858
|
+
function topologyRevision(dir) {
|
|
8859
|
+
const filePath = join(dir, TOPOLOGY_FILE);
|
|
8860
|
+
const raw = exists(filePath) ? readFile(filePath) : "";
|
|
8861
|
+
return crypto2.createHash("sha256").update(raw, "utf-8").digest("hex");
|
|
8862
|
+
}
|
|
8863
|
+
function validateTopologyProposal(dir, proposalYaml) {
|
|
8864
|
+
const { entities, relationships, findings } = parseTopologyContent(proposalYaml, dir);
|
|
8865
|
+
const blocking = findings.filter((f) => f.level === "blocking").length;
|
|
8866
|
+
const warning = findings.filter((f) => f.level === "warning").length;
|
|
8867
|
+
return { findings, blocking, warning, canApply: blocking === 0, entityCount: entities.length, relationshipCount: relationships.length };
|
|
8868
|
+
}
|
|
8869
|
+
function serializeTopology(entities, relationships) {
|
|
8870
|
+
const doc = {
|
|
8871
|
+
entities: entities.map((e) => ({
|
|
8872
|
+
id: e.id,
|
|
8873
|
+
kind: e.kind,
|
|
8874
|
+
label: e.label,
|
|
8875
|
+
...e.purpose ? { purpose: e.purpose } : {},
|
|
8876
|
+
...e.moduleId ? { module: e.moduleId } : {},
|
|
8877
|
+
...e.implementationRefs.length ? { implementation: e.implementationRefs } : {},
|
|
8878
|
+
...e.knowledgeRefs.length ? { knowledge: e.knowledgeRefs } : {},
|
|
8879
|
+
...e.provenance ? { provenance: e.provenance } : {},
|
|
8880
|
+
...e.evidence.length ? { evidence: e.evidence } : {}
|
|
8881
|
+
})),
|
|
8882
|
+
relationships: relationships.map((r) => ({ from: r.from, to: r.to, type: r.type, ...r.evidence.length ? { evidence: r.evidence } : {} }))
|
|
8883
|
+
};
|
|
8884
|
+
return stringifyYaml4(doc);
|
|
8885
|
+
}
|
|
8886
|
+
function atomicWrite2(filePath, content) {
|
|
8887
|
+
fs3.mkdirSync(path3.dirname(filePath), { recursive: true });
|
|
8888
|
+
const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
8889
|
+
fs3.writeFileSync(tmp, content, "utf-8");
|
|
8890
|
+
try {
|
|
8891
|
+
fs3.renameSync(tmp, filePath);
|
|
8892
|
+
} catch (err) {
|
|
8893
|
+
try {
|
|
8894
|
+
fs3.rmSync(tmp, { force: true });
|
|
8895
|
+
} catch {
|
|
8896
|
+
}
|
|
8897
|
+
throw err;
|
|
8898
|
+
}
|
|
8899
|
+
}
|
|
8900
|
+
function applyTopologyProposal(dir, proposalYaml, expectedRevision) {
|
|
8901
|
+
const validation = validateTopologyProposal(dir, proposalYaml);
|
|
8902
|
+
if (!validation.canApply) throw new TopologyWriteError("TOPOLOGY_INVALID", "The topology proposal has blocking findings and cannot be applied.");
|
|
8903
|
+
const current = topologyRevision(dir);
|
|
8904
|
+
if (expectedRevision != null && expectedRevision !== current) {
|
|
8905
|
+
throw new TopologyWriteError("TOPOLOGY_CONFLICT", "The topology changed after this proposal was created. Refresh and validate the proposal again.");
|
|
8906
|
+
}
|
|
8907
|
+
const proposal = parseTopologyContent(proposalYaml, dir);
|
|
8908
|
+
const existing = loadSystemTopology(dir);
|
|
8909
|
+
const entityMap = /* @__PURE__ */ new Map();
|
|
8910
|
+
for (const e of existing.entities) entityMap.set(e.id, e);
|
|
8911
|
+
let entitiesNew = 0, entitiesUpdated = 0;
|
|
8912
|
+
for (const e of proposal.entities) {
|
|
8913
|
+
if (entityMap.has(e.id)) entitiesUpdated++;
|
|
8914
|
+
else entitiesNew++;
|
|
8915
|
+
entityMap.set(e.id, e);
|
|
8916
|
+
}
|
|
8917
|
+
const relKey = (r) => `${r.from}~${r.type}~${r.to}`;
|
|
8918
|
+
const relMap = /* @__PURE__ */ new Map();
|
|
8919
|
+
for (const r of existing.relationships) relMap.set(relKey(r), r);
|
|
8920
|
+
let relationshipsNew = 0;
|
|
8921
|
+
for (const r of proposal.relationships) {
|
|
8922
|
+
if (!relMap.has(relKey(r))) relationshipsNew++;
|
|
8923
|
+
relMap.set(relKey(r), r);
|
|
8924
|
+
}
|
|
8925
|
+
const raw = serializeTopology([...entityMap.values()], [...relMap.values()]);
|
|
8926
|
+
const finalCheck = validateTopologyProposal(dir, raw);
|
|
8927
|
+
if (!finalCheck.canApply) throw new TopologyWriteError("TOPOLOGY_INVALID", "The merged topology is invalid; no changes were written.");
|
|
8928
|
+
atomicWrite2(join(dir, TOPOLOGY_FILE), raw);
|
|
8929
|
+
return { revision: topologyRevision(dir), entitiesNew, entitiesUpdated, relationshipsNew };
|
|
8930
|
+
}
|
|
8830
8931
|
function buildTopologyEnrichmentHandoff(dir, projectName) {
|
|
8831
8932
|
const mappedModules = loadMappedModules(dir).map((m) => m.id);
|
|
8832
8933
|
const lines = [
|
|
@@ -8841,19 +8942,22 @@ function buildTopologyEnrichmentHandoff(dir, projectName) {
|
|
|
8841
8942
|
}
|
|
8842
8943
|
lines.push(
|
|
8843
8944
|
"",
|
|
8844
|
-
|
|
8945
|
+
"Build a topology PROPOSAL first \u2014 do not write canonical metadata directly. For each entity",
|
|
8845
8946
|
"capture, only when supported by evidence:",
|
|
8846
8947
|
"- a stable id and semantic kind (application/service/component/api/interface/datastore/queue/job/external-system);",
|
|
8847
8948
|
"- a responsibility/purpose (what it does, not how);",
|
|
8848
8949
|
"- the owning module/repository;",
|
|
8849
8950
|
"- implementation references (relative paths);",
|
|
8850
|
-
"- relevant Knowledge references (capability/ADR ids), not Kaddo operational assets
|
|
8951
|
+
"- relevant Knowledge references (capability/ADR ids), not Kaddo operational assets;",
|
|
8952
|
+
"- provenance/evidence.",
|
|
8851
8953
|
"",
|
|
8852
8954
|
"Capture evidence-backed technical relationships: contains, calls, depends-on, reads-from,",
|
|
8853
|
-
"writes-to, integrates-with, runs-on. Preserve
|
|
8955
|
+
"writes-to, integrates-with, runs-on. Preserve Unknown when evidence is insufficient.",
|
|
8956
|
+
"",
|
|
8957
|
+
"Validate the proposal through Kaddo before applying it. Do not write canonical topology metadata",
|
|
8958
|
+
`unless (1) Kaddo validation succeeds and (2) the human explicitly confirms the write. The canonical`,
|
|
8959
|
+
`artifact is ${TOPOLOGY_FILE}; Core validates and applies it \u2014 do not hand-edit it.`,
|
|
8854
8960
|
"",
|
|
8855
|
-
"Review the proposed graph metadata (duplicate ids, dangling relationships, unknown modules,",
|
|
8856
|
-
"invalid references) before writing.",
|
|
8857
8961
|
"Do not implement application changes. Do not run mutating Git operations."
|
|
8858
8962
|
);
|
|
8859
8963
|
return {
|
|
@@ -9064,10 +9168,12 @@ function toNode(n, knowledgeByPath, wiModules, groupIds) {
|
|
|
9064
9168
|
}
|
|
9065
9169
|
export {
|
|
9066
9170
|
TOPOLOGY_FILE,
|
|
9171
|
+
TopologyWriteError,
|
|
9067
9172
|
WorkItemNotFoundError,
|
|
9068
9173
|
WorkItemWriteError,
|
|
9069
9174
|
analyzeCrossRepoEvidence,
|
|
9070
9175
|
analyzeScopeCoverage,
|
|
9176
|
+
applyTopologyProposal,
|
|
9071
9177
|
buildProjectExplanation,
|
|
9072
9178
|
buildProjectRoute,
|
|
9073
9179
|
buildReadinessReport,
|
|
@@ -9096,8 +9202,10 @@ export {
|
|
|
9096
9202
|
loadMappedModules,
|
|
9097
9203
|
loadSystemTopology,
|
|
9098
9204
|
readFile,
|
|
9205
|
+
topologyRevision,
|
|
9099
9206
|
transitionWorkItem,
|
|
9100
9207
|
updateWorkItem,
|
|
9101
9208
|
validateSystemTopology,
|
|
9209
|
+
validateTopologyProposal,
|
|
9102
9210
|
validateWorkItem
|
|
9103
9211
|
};
|