@kaddo/cli 3.78.0 → 3.80.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -1361,8 +1361,8 @@ function loadConfig(dir) {
|
|
|
1361
1361
|
const parsed = configSchema.safeParse(raw ?? {});
|
|
1362
1362
|
if (!parsed.success) {
|
|
1363
1363
|
const issues = parsed.error.issues.map((i) => {
|
|
1364
|
-
const
|
|
1365
|
-
return
|
|
1364
|
+
const path10 = i.path.join(".");
|
|
1365
|
+
return path10 ? ` - ${path10}: ${i.message}` : ` - ${i.message}`;
|
|
1366
1366
|
}).join("\n");
|
|
1367
1367
|
throw new ConfigError(`Invalid .kaddo/config.yml:
|
|
1368
1368
|
${issues}`);
|
|
@@ -6732,10 +6732,10 @@ function analyzeGuard(touchedFiles, artifacts, silentWithoutOwnership) {
|
|
|
6732
6732
|
import { parse as parseYaml4, stringify as stringifyYaml2 } from "yaml";
|
|
6733
6733
|
var IGNORE_FILE = ".kaddo/ignores.yml";
|
|
6734
6734
|
function loadIgnores(dir) {
|
|
6735
|
-
const
|
|
6736
|
-
if (!exists(
|
|
6735
|
+
const path10 = join(dir, IGNORE_FILE);
|
|
6736
|
+
if (!exists(path10)) return [];
|
|
6737
6737
|
try {
|
|
6738
|
-
const raw = readFile(
|
|
6738
|
+
const raw = readFile(path10);
|
|
6739
6739
|
const parsed = parseYaml4(raw);
|
|
6740
6740
|
return Array.isArray(parsed) ? parsed : [];
|
|
6741
6741
|
} catch {
|
|
@@ -6743,7 +6743,7 @@ function loadIgnores(dir) {
|
|
|
6743
6743
|
}
|
|
6744
6744
|
}
|
|
6745
6745
|
function saveIgnore(dir, entry) {
|
|
6746
|
-
const
|
|
6746
|
+
const path10 = join(dir, IGNORE_FILE);
|
|
6747
6747
|
const existing = loadIgnores(dir);
|
|
6748
6748
|
const idx = existing.findIndex((e) => e.artifact_id === entry.artifact_id);
|
|
6749
6749
|
if (idx >= 0) {
|
|
@@ -6751,17 +6751,17 @@ function saveIgnore(dir, entry) {
|
|
|
6751
6751
|
} else {
|
|
6752
6752
|
existing.push(entry);
|
|
6753
6753
|
}
|
|
6754
|
-
writeFile(
|
|
6754
|
+
writeFile(path10, stringifyYaml2(existing));
|
|
6755
6755
|
}
|
|
6756
6756
|
function isIgnored(ignores, artifactId) {
|
|
6757
6757
|
return ignores.find((e) => e.artifact_id === artifactId);
|
|
6758
6758
|
}
|
|
6759
6759
|
function removeIgnore(dir, artifactId) {
|
|
6760
|
-
const
|
|
6760
|
+
const path10 = join(dir, IGNORE_FILE);
|
|
6761
6761
|
const existing = loadIgnores(dir);
|
|
6762
6762
|
const filtered = existing.filter((e) => e.artifact_id !== artifactId);
|
|
6763
6763
|
if (filtered.length === existing.length) return false;
|
|
6764
|
-
writeFile(
|
|
6764
|
+
writeFile(path10, stringifyYaml2(filtered));
|
|
6765
6765
|
return true;
|
|
6766
6766
|
}
|
|
6767
6767
|
|
|
@@ -6783,11 +6783,11 @@ function moduleArtifactCoverage(dir, id) {
|
|
|
6783
6783
|
};
|
|
6784
6784
|
}
|
|
6785
6785
|
function loadMappedModules(dir) {
|
|
6786
|
-
const
|
|
6787
|
-
if (!exists(
|
|
6786
|
+
const path10 = join(dir, DESCRIPTOR_PATH);
|
|
6787
|
+
if (!exists(path10)) return [];
|
|
6788
6788
|
let parsed;
|
|
6789
6789
|
try {
|
|
6790
|
-
parsed = parseYaml5(readFile(
|
|
6790
|
+
parsed = parseYaml5(readFile(path10));
|
|
6791
6791
|
} catch {
|
|
6792
6792
|
return [];
|
|
6793
6793
|
}
|
|
@@ -7531,7 +7531,7 @@ function buildGuardHistory(dir) {
|
|
|
7531
7531
|
const d = hotspotDir(t.code_path);
|
|
7532
7532
|
hotspotMap.set(d, (hotspotMap.get(d) ?? 0) + 1);
|
|
7533
7533
|
}
|
|
7534
|
-
const hotspots = [...hotspotMap.entries()].map(([
|
|
7534
|
+
const hotspots = [...hotspotMap.entries()].map(([path10, warnings]) => ({ path: path10, warnings })).sort((a, b) => b.warnings - a.warnings);
|
|
7535
7535
|
return {
|
|
7536
7536
|
available: true,
|
|
7537
7537
|
total_runs: sorted.length,
|
|
@@ -10102,10 +10102,10 @@ function slugify2(name) {
|
|
|
10102
10102
|
return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
10103
10103
|
}
|
|
10104
10104
|
function readModulesDescriptor(dir) {
|
|
10105
|
-
const
|
|
10106
|
-
if (!exists(
|
|
10105
|
+
const path10 = join(dir, DESCRIPTOR_PATH2);
|
|
10106
|
+
if (!exists(path10)) return { version: 1, modules: [] };
|
|
10107
10107
|
try {
|
|
10108
|
-
const parsed = parseYaml9(readFile(
|
|
10108
|
+
const parsed = parseYaml9(readFile(path10));
|
|
10109
10109
|
return { version: parsed.version ?? 1, modules: parsed.modules ?? [] };
|
|
10110
10110
|
} catch {
|
|
10111
10111
|
return { version: 1, modules: [] };
|
|
@@ -10485,7 +10485,7 @@ function sectionParagraph(md, title) {
|
|
|
10485
10485
|
}
|
|
10486
10486
|
return "";
|
|
10487
10487
|
}
|
|
10488
|
-
function parseCapsule(id,
|
|
10488
|
+
function parseCapsule(id, path10, md) {
|
|
10489
10489
|
const { data } = matter3(md);
|
|
10490
10490
|
const updatedAt = data.updated_at ? String(data.updated_at) : void 0;
|
|
10491
10491
|
let ageDays = null;
|
|
@@ -10495,7 +10495,7 @@ function parseCapsule(id, path9, md) {
|
|
|
10495
10495
|
}
|
|
10496
10496
|
return {
|
|
10497
10497
|
id,
|
|
10498
|
-
path:
|
|
10498
|
+
path: path10,
|
|
10499
10499
|
system: data.system ? String(data.system) : id,
|
|
10500
10500
|
owner: data.owner ? String(data.owner) : void 0,
|
|
10501
10501
|
updatedAt,
|
|
@@ -10581,9 +10581,9 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
10581
10581
|
];
|
|
10582
10582
|
const presentLayers = [];
|
|
10583
10583
|
for (const layer2 of layerDocs) {
|
|
10584
|
-
const
|
|
10585
|
-
if (
|
|
10586
|
-
addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path:
|
|
10584
|
+
const path10 = layer2.files.map((f) => `${KNOWLEDGE3}/${f}`).find((rel) => exists(join(dir, rel)));
|
|
10585
|
+
if (path10) {
|
|
10586
|
+
addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path: path10 });
|
|
10587
10587
|
presentLayers.push(layer2.id);
|
|
10588
10588
|
}
|
|
10589
10589
|
}
|
|
@@ -12119,10 +12119,10 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
|
|
|
12119
12119
|
const q = (rel) => analyzeKnowledgeArtifact(dir, rel);
|
|
12120
12120
|
const resolveAgent = (agent) => {
|
|
12121
12121
|
const file = agent.endsWith(".md") ? agent : `${agent}.md`;
|
|
12122
|
-
const
|
|
12123
|
-
const installed = isFile(join(dir,
|
|
12122
|
+
const path10 = agentInstallPath(file);
|
|
12123
|
+
const installed = isFile(join(dir, path10));
|
|
12124
12124
|
const group = agentGroupOf(file);
|
|
12125
|
-
return { agentPath:
|
|
12125
|
+
return { agentPath: path10, agentInstalled: installed, installCommand: installed ? void 0 : `kaddo add agents --group ${group}` };
|
|
12126
12126
|
};
|
|
12127
12127
|
const moduleRepo = isModule(config);
|
|
12128
12128
|
const coreRepo = isCore(config);
|
|
@@ -17097,6 +17097,287 @@ function runGraphExport(opts = {}) {
|
|
|
17097
17097
|
outro2("Knowledge graph ready.");
|
|
17098
17098
|
}
|
|
17099
17099
|
|
|
17100
|
+
// src/core/system-topology.ts
|
|
17101
|
+
import { parse as parseYaml16, stringify as stringifyYaml7 } from "yaml";
|
|
17102
|
+
import fs3 from "fs";
|
|
17103
|
+
import path7 from "path";
|
|
17104
|
+
import crypto from "crypto";
|
|
17105
|
+
var TOPOLOGY_FILE = "knowledge/tech/system-topology.yml";
|
|
17106
|
+
var SYSTEM_ENTITY_KINDS = /* @__PURE__ */ new Set([
|
|
17107
|
+
"system",
|
|
17108
|
+
"application",
|
|
17109
|
+
"service",
|
|
17110
|
+
"component",
|
|
17111
|
+
"api",
|
|
17112
|
+
"interface",
|
|
17113
|
+
"datastore",
|
|
17114
|
+
"queue",
|
|
17115
|
+
"job",
|
|
17116
|
+
"external-system",
|
|
17117
|
+
"module",
|
|
17118
|
+
"unknown"
|
|
17119
|
+
]);
|
|
17120
|
+
var TECHNICAL_RELATIONSHIP_TYPES = /* @__PURE__ */ new Set([
|
|
17121
|
+
"contains",
|
|
17122
|
+
"depends-on",
|
|
17123
|
+
"calls",
|
|
17124
|
+
"reads-from",
|
|
17125
|
+
"writes-to",
|
|
17126
|
+
"publishes-to",
|
|
17127
|
+
"subscribes-to",
|
|
17128
|
+
"integrates-with",
|
|
17129
|
+
"runs-on",
|
|
17130
|
+
"implemented-by"
|
|
17131
|
+
]);
|
|
17132
|
+
var PROVENANCE = /* @__PURE__ */ new Set(["declared", "derived", "agent-reviewed"]);
|
|
17133
|
+
function isRelative(p2) {
|
|
17134
|
+
return typeof p2 === "string" && p2.trim() !== "" && !/^([a-zA-Z]:[\\/]|\/)/.test(p2) && !p2.includes("..");
|
|
17135
|
+
}
|
|
17136
|
+
function strList(v) {
|
|
17137
|
+
return Array.isArray(v) ? v.map((x) => typeof x === "string" ? x.trim() : "").filter(Boolean) : [];
|
|
17138
|
+
}
|
|
17139
|
+
function loadSystemTopology(dir) {
|
|
17140
|
+
const filePath = join(dir, TOPOLOGY_FILE);
|
|
17141
|
+
if (!exists(filePath)) return { entities: [], relationships: [], findings: [], declared: false };
|
|
17142
|
+
const parsed = parseTopologyContent(readFile(filePath), dir);
|
|
17143
|
+
return { ...parsed, declared: true };
|
|
17144
|
+
}
|
|
17145
|
+
function parseTopologyContent(raw, dir) {
|
|
17146
|
+
let parsed;
|
|
17147
|
+
try {
|
|
17148
|
+
parsed = parseYaml16(raw) ?? {};
|
|
17149
|
+
} catch {
|
|
17150
|
+
return { entities: [], relationships: [], findings: [{ level: "blocking", message: "The topology could not be parsed." }] };
|
|
17151
|
+
}
|
|
17152
|
+
const sourceRevision = typeof parsed.source_revision === "string" ? parsed.source_revision : void 0;
|
|
17153
|
+
const findings = [];
|
|
17154
|
+
const validModules = /* @__PURE__ */ new Set(["core", ...loadMappedModules(dir).map((m) => m.id)]);
|
|
17155
|
+
const rawEntities = Array.isArray(parsed.entities) ? parsed.entities : [];
|
|
17156
|
+
const entities = [];
|
|
17157
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
17158
|
+
for (const raw2 of rawEntities) {
|
|
17159
|
+
if (!raw2 || typeof raw2 !== "object") continue;
|
|
17160
|
+
const e = raw2;
|
|
17161
|
+
const id = typeof e.id === "string" ? e.id.trim() : "";
|
|
17162
|
+
if (!id) {
|
|
17163
|
+
findings.push({ level: "warning", message: "A topology entity is missing an id and was skipped." });
|
|
17164
|
+
continue;
|
|
17165
|
+
}
|
|
17166
|
+
if (seenIds.has(id)) {
|
|
17167
|
+
findings.push({ level: "blocking", message: `Duplicate topology entity id "${id}".` });
|
|
17168
|
+
continue;
|
|
17169
|
+
}
|
|
17170
|
+
const kind = typeof e.kind === "string" ? e.kind.trim() : "unknown";
|
|
17171
|
+
if (!SYSTEM_ENTITY_KINDS.has(kind)) {
|
|
17172
|
+
findings.push({ level: "warning", message: `Entity "${id}" has an unknown kind "${kind}"; treated as unknown.` });
|
|
17173
|
+
}
|
|
17174
|
+
const moduleRaw = typeof e.module === "string" ? e.module.trim() : void 0;
|
|
17175
|
+
if (moduleRaw && !validModules.has(moduleRaw)) findings.push({ level: "warning", message: `Entity "${id}" references unregistered module "${moduleRaw}".` });
|
|
17176
|
+
const rawImpl = e.implementation ?? e.implementation_refs;
|
|
17177
|
+
const implementation = strList(rawImpl).filter((p2) => {
|
|
17178
|
+
if (isRelative(p2)) return true;
|
|
17179
|
+
findings.push({ level: "warning", message: `Entity "${id}" implementation ref "${p2}" is not a safe relative path and was dropped.` });
|
|
17180
|
+
return false;
|
|
17181
|
+
});
|
|
17182
|
+
const prov = e.provenance;
|
|
17183
|
+
const provOrigin = typeof prov === "string" ? prov : prov && typeof prov === "object" && typeof prov.origin === "string" ? String(prov.origin) : void 0;
|
|
17184
|
+
const provenance = provOrigin && PROVENANCE.has(provOrigin) ? provOrigin : void 0;
|
|
17185
|
+
const evidence = strList(e.evidence ?? (prov && typeof prov === "object" ? prov.evidence_refs : void 0)).filter(isRelative);
|
|
17186
|
+
seenIds.add(id);
|
|
17187
|
+
entities.push({
|
|
17188
|
+
id,
|
|
17189
|
+
kind: SYSTEM_ENTITY_KINDS.has(kind) ? kind : "unknown",
|
|
17190
|
+
label: typeof e.label === "string" && e.label.trim() ? e.label.trim() : id,
|
|
17191
|
+
...typeof e.purpose === "string" && e.purpose.trim() ? { purpose: e.purpose.trim() } : {},
|
|
17192
|
+
...moduleRaw && validModules.has(moduleRaw) ? { moduleId: moduleRaw } : {},
|
|
17193
|
+
implementationRefs: implementation,
|
|
17194
|
+
knowledgeRefs: strList(e.knowledge ?? e.knowledge_refs),
|
|
17195
|
+
...provenance ? { provenance } : {},
|
|
17196
|
+
evidence
|
|
17197
|
+
});
|
|
17198
|
+
}
|
|
17199
|
+
const entityIds = new Set(entities.map((e) => e.id));
|
|
17200
|
+
const rawRels = Array.isArray(parsed.relationships) ? parsed.relationships : [];
|
|
17201
|
+
const relationships = [];
|
|
17202
|
+
const seenRels = /* @__PURE__ */ new Set();
|
|
17203
|
+
for (const raw2 of rawRels) {
|
|
17204
|
+
if (!raw2 || typeof raw2 !== "object") continue;
|
|
17205
|
+
const r = raw2;
|
|
17206
|
+
const from = typeof r.from === "string" ? r.from.trim() : typeof r.source === "string" ? r.source.trim() : "";
|
|
17207
|
+
const to = typeof r.to === "string" ? r.to.trim() : typeof r.target === "string" ? r.target.trim() : "";
|
|
17208
|
+
const type = typeof r.type === "string" ? r.type.trim() : "";
|
|
17209
|
+
if (!from || !to || !type) {
|
|
17210
|
+
findings.push({ level: "warning", message: "A topology relationship is missing from/to/type and was skipped." });
|
|
17211
|
+
continue;
|
|
17212
|
+
}
|
|
17213
|
+
if (!TECHNICAL_RELATIONSHIP_TYPES.has(type)) {
|
|
17214
|
+
findings.push({ level: "warning", message: `Relationship type "${type}" is not recognized and was skipped.` });
|
|
17215
|
+
continue;
|
|
17216
|
+
}
|
|
17217
|
+
if (!entityIds.has(from) || !entityIds.has(to)) {
|
|
17218
|
+
findings.push({ level: "blocking", message: `Relationship ${from} \u2192 ${to} references an unknown entity endpoint.` });
|
|
17219
|
+
continue;
|
|
17220
|
+
}
|
|
17221
|
+
const key = `${from}~${type}~${to}`;
|
|
17222
|
+
if (seenRels.has(key)) {
|
|
17223
|
+
findings.push({ level: "warning", message: `Duplicate relationship ${from} ${type} ${to} was skipped.` });
|
|
17224
|
+
continue;
|
|
17225
|
+
}
|
|
17226
|
+
seenRels.add(key);
|
|
17227
|
+
const prov = r.provenance;
|
|
17228
|
+
relationships.push({ from, to, type, evidence: strList(r.evidence ?? prov?.evidence_refs).filter(isRelative) });
|
|
17229
|
+
}
|
|
17230
|
+
return { entities, relationships, findings, ...sourceRevision ? { sourceRevision } : {} };
|
|
17231
|
+
}
|
|
17232
|
+
var TopologyWriteError = class extends Error {
|
|
17233
|
+
code;
|
|
17234
|
+
constructor(code, message) {
|
|
17235
|
+
super(message);
|
|
17236
|
+
this.name = "TopologyWriteError";
|
|
17237
|
+
this.code = code;
|
|
17238
|
+
}
|
|
17239
|
+
};
|
|
17240
|
+
function topologyRevision(dir) {
|
|
17241
|
+
const filePath = join(dir, TOPOLOGY_FILE);
|
|
17242
|
+
const raw = exists(filePath) ? readFile(filePath) : "";
|
|
17243
|
+
return crypto.createHash("sha256").update(raw, "utf-8").digest("hex");
|
|
17244
|
+
}
|
|
17245
|
+
function validateTopologyProposal(dir, proposalYaml) {
|
|
17246
|
+
const { entities, relationships, findings } = parseTopologyContent(proposalYaml, dir);
|
|
17247
|
+
const blocking = findings.filter((f) => f.level === "blocking").length;
|
|
17248
|
+
const warning = findings.filter((f) => f.level === "warning").length;
|
|
17249
|
+
return { findings, blocking, warning, canApply: blocking === 0, entityCount: entities.length, relationshipCount: relationships.length };
|
|
17250
|
+
}
|
|
17251
|
+
function serializeTopology(entities, relationships) {
|
|
17252
|
+
const doc = {
|
|
17253
|
+
entities: entities.map((e) => ({
|
|
17254
|
+
id: e.id,
|
|
17255
|
+
kind: e.kind,
|
|
17256
|
+
label: e.label,
|
|
17257
|
+
...e.purpose ? { purpose: e.purpose } : {},
|
|
17258
|
+
...e.moduleId ? { module: e.moduleId } : {},
|
|
17259
|
+
...e.implementationRefs.length ? { implementation: e.implementationRefs } : {},
|
|
17260
|
+
...e.knowledgeRefs.length ? { knowledge: e.knowledgeRefs } : {},
|
|
17261
|
+
...e.provenance ? { provenance: e.provenance } : {},
|
|
17262
|
+
...e.evidence.length ? { evidence: e.evidence } : {}
|
|
17263
|
+
})),
|
|
17264
|
+
relationships: relationships.map((r) => ({ from: r.from, to: r.to, type: r.type, ...r.evidence.length ? { evidence: r.evidence } : {} }))
|
|
17265
|
+
};
|
|
17266
|
+
return stringifyYaml7(doc);
|
|
17267
|
+
}
|
|
17268
|
+
function atomicWrite(filePath, content) {
|
|
17269
|
+
fs3.mkdirSync(path7.dirname(filePath), { recursive: true });
|
|
17270
|
+
const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
17271
|
+
fs3.writeFileSync(tmp, content, "utf-8");
|
|
17272
|
+
try {
|
|
17273
|
+
fs3.renameSync(tmp, filePath);
|
|
17274
|
+
} catch (err) {
|
|
17275
|
+
try {
|
|
17276
|
+
fs3.rmSync(tmp, { force: true });
|
|
17277
|
+
} catch {
|
|
17278
|
+
}
|
|
17279
|
+
throw err;
|
|
17280
|
+
}
|
|
17281
|
+
}
|
|
17282
|
+
function applyTopologyProposal(dir, proposalYaml, expectedRevision) {
|
|
17283
|
+
const validation = validateTopologyProposal(dir, proposalYaml);
|
|
17284
|
+
if (!validation.canApply) throw new TopologyWriteError("TOPOLOGY_INVALID", "The topology proposal has blocking findings and cannot be applied.");
|
|
17285
|
+
const current = topologyRevision(dir);
|
|
17286
|
+
if (expectedRevision != null && expectedRevision !== current) {
|
|
17287
|
+
throw new TopologyWriteError("TOPOLOGY_CONFLICT", "The topology changed after this proposal was created. Refresh and validate the proposal again.");
|
|
17288
|
+
}
|
|
17289
|
+
const proposal = parseTopologyContent(proposalYaml, dir);
|
|
17290
|
+
const existing = loadSystemTopology(dir);
|
|
17291
|
+
const entityMap = /* @__PURE__ */ new Map();
|
|
17292
|
+
for (const e of existing.entities) entityMap.set(e.id, e);
|
|
17293
|
+
let entitiesNew = 0, entitiesUpdated = 0;
|
|
17294
|
+
for (const e of proposal.entities) {
|
|
17295
|
+
if (entityMap.has(e.id)) entitiesUpdated++;
|
|
17296
|
+
else entitiesNew++;
|
|
17297
|
+
entityMap.set(e.id, e);
|
|
17298
|
+
}
|
|
17299
|
+
const relKey = (r) => `${r.from}~${r.type}~${r.to}`;
|
|
17300
|
+
const relMap = /* @__PURE__ */ new Map();
|
|
17301
|
+
for (const r of existing.relationships) relMap.set(relKey(r), r);
|
|
17302
|
+
let relationshipsNew = 0;
|
|
17303
|
+
for (const r of proposal.relationships) {
|
|
17304
|
+
if (!relMap.has(relKey(r))) relationshipsNew++;
|
|
17305
|
+
relMap.set(relKey(r), r);
|
|
17306
|
+
}
|
|
17307
|
+
const raw = serializeTopology([...entityMap.values()], [...relMap.values()]);
|
|
17308
|
+
const finalCheck = validateTopologyProposal(dir, raw);
|
|
17309
|
+
if (!finalCheck.canApply) throw new TopologyWriteError("TOPOLOGY_INVALID", "The merged topology is invalid; no changes were written.");
|
|
17310
|
+
atomicWrite(join(dir, TOPOLOGY_FILE), raw);
|
|
17311
|
+
return { revision: topologyRevision(dir), entitiesNew, entitiesUpdated, relationshipsNew };
|
|
17312
|
+
}
|
|
17313
|
+
|
|
17314
|
+
// src/commands/topology.ts
|
|
17315
|
+
function requireProject(dir) {
|
|
17316
|
+
if (!loadConfig(dir)) {
|
|
17317
|
+
log2.error("No Kaddo project was found in the current directory.");
|
|
17318
|
+
process.exit(1);
|
|
17319
|
+
}
|
|
17320
|
+
}
|
|
17321
|
+
function readProposal(dir, file) {
|
|
17322
|
+
const abs = join(dir, file);
|
|
17323
|
+
if (!exists(abs) && !exists(file)) {
|
|
17324
|
+
log2.error(`Proposal file not found: ${file}`);
|
|
17325
|
+
process.exit(1);
|
|
17326
|
+
}
|
|
17327
|
+
return readFile(exists(abs) ? abs : file);
|
|
17328
|
+
}
|
|
17329
|
+
function printFindings(v) {
|
|
17330
|
+
log2.info(`${v.entityCount} entities \xB7 ${v.relationshipCount} relationships`);
|
|
17331
|
+
log2.info(`${v.blocking} blocking \xB7 ${v.warning} warning`);
|
|
17332
|
+
for (const f of v.findings) {
|
|
17333
|
+
const line = `[${f.level}] ${f.message}`;
|
|
17334
|
+
if (f.level === "blocking") log2.error(line);
|
|
17335
|
+
else log2.warn(line);
|
|
17336
|
+
}
|
|
17337
|
+
}
|
|
17338
|
+
async function runTopologyValidate(file) {
|
|
17339
|
+
const dir = cwd();
|
|
17340
|
+
requireProject(dir);
|
|
17341
|
+
intro2("kaddo topology validate");
|
|
17342
|
+
const proposal = readProposal(dir, file);
|
|
17343
|
+
const v = validateTopologyProposal(dir, proposal);
|
|
17344
|
+
printFindings(v);
|
|
17345
|
+
if (v.canApply) outro2("Proposal is valid. Apply it with `kaddo topology apply`.");
|
|
17346
|
+
else {
|
|
17347
|
+
outro2("Proposal has blocking findings and cannot be applied.");
|
|
17348
|
+
process.exit(1);
|
|
17349
|
+
}
|
|
17350
|
+
}
|
|
17351
|
+
async function runTopologyApply(file, opts = {}) {
|
|
17352
|
+
const dir = cwd();
|
|
17353
|
+
requireProject(dir);
|
|
17354
|
+
intro2("kaddo topology apply");
|
|
17355
|
+
const proposal = readProposal(dir, file);
|
|
17356
|
+
const v = validateTopologyProposal(dir, proposal);
|
|
17357
|
+
printFindings(v);
|
|
17358
|
+
if (!v.canApply) {
|
|
17359
|
+
cancel2("Cannot apply: the proposal has blocking findings.");
|
|
17360
|
+
process.exit(1);
|
|
17361
|
+
}
|
|
17362
|
+
if (!opts.yes) {
|
|
17363
|
+
const ok = await confirm2({ message: `Apply this topology enrichment to ${join("", "knowledge/tech/system-topology.yml")}? (writes the project artifact; no Git commit is created)` });
|
|
17364
|
+
if (!ok) {
|
|
17365
|
+
cancel2("Apply cancelled.");
|
|
17366
|
+
process.exit(0);
|
|
17367
|
+
}
|
|
17368
|
+
}
|
|
17369
|
+
try {
|
|
17370
|
+
const result = applyTopologyProposal(dir, proposal, topologyRevision(dir));
|
|
17371
|
+
outro2(`Applied. ${result.entitiesNew} new / ${result.entitiesUpdated} updated entities, ${result.relationshipsNew} new relationships.`);
|
|
17372
|
+
} catch (err) {
|
|
17373
|
+
if (err instanceof TopologyWriteError) {
|
|
17374
|
+
cancel2(err.message);
|
|
17375
|
+
process.exit(1);
|
|
17376
|
+
}
|
|
17377
|
+
throw err;
|
|
17378
|
+
}
|
|
17379
|
+
}
|
|
17380
|
+
|
|
17100
17381
|
// src/core/impact-report.ts
|
|
17101
17382
|
import matter10 from "gray-matter";
|
|
17102
17383
|
function isBroadGlob(glob) {
|
|
@@ -17544,7 +17825,7 @@ function runReportImpact(opts = {}) {
|
|
|
17544
17825
|
}
|
|
17545
17826
|
|
|
17546
17827
|
// src/core/savings.ts
|
|
17547
|
-
import { parse as
|
|
17828
|
+
import { parse as parseYaml17 } from "yaml";
|
|
17548
17829
|
var DEFAULT_ASSUMPTIONS = {
|
|
17549
17830
|
currency: "USD",
|
|
17550
17831
|
hourly_cost: 40,
|
|
@@ -17562,7 +17843,7 @@ function loadAssumptions(dir) {
|
|
|
17562
17843
|
const p2 = join(dir, SAVINGS_PATH);
|
|
17563
17844
|
if (!exists(p2)) return { assumptions: { ...DEFAULT_ASSUMPTIONS }, source: "default" };
|
|
17564
17845
|
try {
|
|
17565
|
-
const raw =
|
|
17846
|
+
const raw = parseYaml17(readFile(p2));
|
|
17566
17847
|
const a = raw?.assumptions ?? {};
|
|
17567
17848
|
const t = raw?.team ?? {};
|
|
17568
17849
|
const num = (v, d) => typeof v === "number" && Number.isFinite(v) ? v : d;
|
|
@@ -18244,7 +18525,7 @@ function runAdr(opts = {}) {
|
|
|
18244
18525
|
}
|
|
18245
18526
|
|
|
18246
18527
|
// src/commands/tech.ts
|
|
18247
|
-
import
|
|
18528
|
+
import fs4 from "fs";
|
|
18248
18529
|
var DISCOVERY_FILES = ["architecture-notes.md", "decision-candidates.md"];
|
|
18249
18530
|
var DISCOVERY_DIR = "knowledge/tech/discovery";
|
|
18250
18531
|
function runTechOrganize(dir = cwd()) {
|
|
@@ -18261,7 +18542,7 @@ function runTechOrganize(dir = cwd()) {
|
|
|
18261
18542
|
continue;
|
|
18262
18543
|
}
|
|
18263
18544
|
ensureDir(join(dir, DISCOVERY_DIR));
|
|
18264
|
-
|
|
18545
|
+
fs4.renameSync(from, to);
|
|
18265
18546
|
moved.push(`knowledge/tech/${file} \u2192 ${DISCOVERY_DIR}/${file}`);
|
|
18266
18547
|
}
|
|
18267
18548
|
if (moved.length > 0) {
|
|
@@ -18343,8 +18624,8 @@ function runAssetsUpdate(kind, opts = {}) {
|
|
|
18343
18624
|
}
|
|
18344
18625
|
|
|
18345
18626
|
// src/commands/ready.ts
|
|
18346
|
-
import
|
|
18347
|
-
import
|
|
18627
|
+
import fs5 from "fs";
|
|
18628
|
+
import path8 from "path";
|
|
18348
18629
|
import matter11 from "gray-matter";
|
|
18349
18630
|
function validateReadiness(content, frontmatter) {
|
|
18350
18631
|
const warnings = [];
|
|
@@ -18407,10 +18688,10 @@ function transitionToReady(filePath) {
|
|
|
18407
18688
|
const posix = filePath.replace(/\\/g, "/");
|
|
18408
18689
|
let newPath = filePath;
|
|
18409
18690
|
if (posix.includes("/work-items/draft/")) {
|
|
18410
|
-
const readyDir =
|
|
18691
|
+
const readyDir = path8.dirname(filePath).replace(/[/\\]draft$/, path8.sep + "ready");
|
|
18411
18692
|
ensureDir(readyDir);
|
|
18412
|
-
const fileName =
|
|
18413
|
-
newPath =
|
|
18693
|
+
const fileName = path8.basename(filePath);
|
|
18694
|
+
newPath = path8.join(readyDir, fileName);
|
|
18414
18695
|
}
|
|
18415
18696
|
return { newPath, newContent };
|
|
18416
18697
|
}
|
|
@@ -18478,11 +18759,11 @@ async function runReady(id, opts = {}) {
|
|
|
18478
18759
|
const { newPath, newContent } = transitionToReady(wi.filePath);
|
|
18479
18760
|
if (newPath !== wi.filePath) {
|
|
18480
18761
|
writeFile(newPath, newContent);
|
|
18481
|
-
|
|
18762
|
+
fs5.unlinkSync(wi.filePath);
|
|
18482
18763
|
log2.success(`Updated status: ready`);
|
|
18483
18764
|
log2.success(`Moved file:`);
|
|
18484
|
-
log2.info(` ${
|
|
18485
|
-
log2.info(` \u2192 ${
|
|
18765
|
+
log2.info(` ${path8.relative(dir, wi.filePath)}`);
|
|
18766
|
+
log2.info(` \u2192 ${path8.relative(dir, newPath)}`);
|
|
18486
18767
|
} else {
|
|
18487
18768
|
writeFile(wi.filePath, newContent);
|
|
18488
18769
|
log2.success(`Updated status: ready`);
|
|
@@ -18493,7 +18774,7 @@ async function runReady(id, opts = {}) {
|
|
|
18493
18774
|
}
|
|
18494
18775
|
|
|
18495
18776
|
// src/commands/admin.ts
|
|
18496
|
-
import
|
|
18777
|
+
import path9 from "path";
|
|
18497
18778
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
18498
18779
|
import net from "net";
|
|
18499
18780
|
function isPortAvailable(port, host) {
|
|
@@ -18508,13 +18789,13 @@ function isPortAvailable(port, host) {
|
|
|
18508
18789
|
});
|
|
18509
18790
|
}
|
|
18510
18791
|
function resolveStaticDir() {
|
|
18511
|
-
const __dirname =
|
|
18792
|
+
const __dirname = path9.dirname(fileURLToPath2(import.meta.url));
|
|
18512
18793
|
const candidates = [
|
|
18513
18794
|
// Bundled inside CLI dist (npm install)
|
|
18514
|
-
|
|
18795
|
+
path9.resolve(__dirname, "admin-dist"),
|
|
18515
18796
|
// Monorepo development
|
|
18516
|
-
|
|
18517
|
-
|
|
18797
|
+
path9.resolve(__dirname, "..", "..", "admin", "dist"),
|
|
18798
|
+
path9.resolve(__dirname, "..", "node_modules", "@kaddo", "admin", "dist")
|
|
18518
18799
|
];
|
|
18519
18800
|
for (const c of candidates) {
|
|
18520
18801
|
if (exists(join(c, "index.html"))) return c;
|
|
@@ -18556,8 +18837,8 @@ async function runAdmin(opts = {}) {
|
|
|
18556
18837
|
}
|
|
18557
18838
|
let adminServer;
|
|
18558
18839
|
try {
|
|
18559
|
-
const __dirname =
|
|
18560
|
-
const bundled =
|
|
18840
|
+
const __dirname = path9.dirname(fileURLToPath2(import.meta.url));
|
|
18841
|
+
const bundled = path9.resolve(__dirname, "admin-server", "index.js");
|
|
18561
18842
|
if (exists(bundled)) {
|
|
18562
18843
|
const { pathToFileURL } = await import("url");
|
|
18563
18844
|
adminServer = await import(pathToFileURL(bundled).href);
|
|
@@ -18636,13 +18917,20 @@ var capsuleCmd = program.command("capsule").description("Export this project as
|
|
|
18636
18917
|
capsuleCmd.command("export").description("Write a Knowledge Capsule about this project to .kaddo/exports/").option("--scope <scope>", 'Export scope: "system" includes all mapped modules').option("--module <id>", "Export a capsule for a specific mapped module").action((opts) => {
|
|
18637
18918
|
runCapsuleExport(opts);
|
|
18638
18919
|
});
|
|
18639
|
-
capsuleCmd.command("add <path>").description("Register an external Knowledge Capsule as project context (.kaddo/external.yml)").action((
|
|
18640
|
-
runCapsuleAdd(
|
|
18920
|
+
capsuleCmd.command("add <path>").description("Register an external Knowledge Capsule as project context (.kaddo/external.yml)").action((path10) => {
|
|
18921
|
+
runCapsuleAdd(path10);
|
|
18641
18922
|
});
|
|
18642
18923
|
var graphCmd = program.command("graph").description("Export the lightweight, file-based knowledge graph of the project");
|
|
18643
18924
|
graphCmd.command("export").description("Write the knowledge graph to .kaddo/graph.json and .kaddo/graph.mmd").option("--scope <scope>", "Graph scope: active (default) or all").option("--format <format>", "Output format: json, mermaid (default: both)").action((opts) => {
|
|
18644
18925
|
runGraphExport(opts);
|
|
18645
18926
|
});
|
|
18927
|
+
var topologyCmd = program.command("topology").description("Validate and apply a semantic system topology proposal (agent proposes, human confirms, Core applies)");
|
|
18928
|
+
topologyCmd.command("validate <file>").description("Validate a topology proposal file deterministically (no write)").action((file) => {
|
|
18929
|
+
runTopologyValidate(file);
|
|
18930
|
+
});
|
|
18931
|
+
topologyCmd.command("apply <file>").description("Apply a validated topology proposal to the canonical artifact after human confirmation").option("-y, --yes", "Skip the confirmation prompt (for already-approved automation)").action((file, opts) => {
|
|
18932
|
+
runTopologyApply(file, opts);
|
|
18933
|
+
});
|
|
18646
18934
|
var reportCmd = program.command("report").description("Generate Kaddo reports");
|
|
18647
18935
|
reportCmd.command("impact").description("Knowledge Impact Report: knowledge health, coverage, traceability, readiness (deterministic, no LLM)").option("--json", "Output JSON instead of Markdown").option("--scope <scope>", "Scope: all (default \u2014 accumulated impact) or active").option("--output <path>", "Write the report to a file (e.g. .kaddo/reports/impact-report.md)").action((opts) => {
|
|
18648
18936
|
runReportImpact(opts);
|