@kaddo/cli 3.64.0 → 3.66.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 +405 -73
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6401,7 +6401,14 @@ function parseArtifact(filePath, raw) {
|
|
|
6401
6401
|
sourceId: String(data.source_id ?? ""),
|
|
6402
6402
|
rawFrontmatter: data,
|
|
6403
6403
|
decisions: Array.isArray(data.decisions) ? data.decisions.map(String).filter(Boolean) : [],
|
|
6404
|
-
capsules: Array.isArray(data.capsules) ? data.capsules.map(String).filter(Boolean) : []
|
|
6404
|
+
capsules: Array.isArray(data.capsules) ? data.capsules.map(String).filter(Boolean) : [],
|
|
6405
|
+
implementationStatus: String(data.implementation_status ?? ""),
|
|
6406
|
+
validationStatus: String(data.validation_status ?? ""),
|
|
6407
|
+
releaseStatus: String(data.release_status ?? ""),
|
|
6408
|
+
affectedModules: Array.isArray(data.affected_modules) ? data.affected_modules.map(String).filter(Boolean) : [],
|
|
6409
|
+
refinedBy: String(data.refined_by ?? ""),
|
|
6410
|
+
implementedBy: String(data.implemented_by ?? ""),
|
|
6411
|
+
closedBy: String(data.closed_by ?? "")
|
|
6405
6412
|
};
|
|
6406
6413
|
} catch {
|
|
6407
6414
|
return null;
|
|
@@ -6833,9 +6840,125 @@ function analyzeMetadataHealth(dir) {
|
|
|
6833
6840
|
if (fileDrifted) drifted++;
|
|
6834
6841
|
else healthy++;
|
|
6835
6842
|
}
|
|
6843
|
+
const wiDir = join(dir, "knowledge/delivery/work-items");
|
|
6844
|
+
if (exists(wiDir)) {
|
|
6845
|
+
const walkWIs = (d) => {
|
|
6846
|
+
for (const entry of readDir(d)) {
|
|
6847
|
+
const p2 = join(d, entry);
|
|
6848
|
+
if (isFile(p2) && entry.endsWith(".md")) {
|
|
6849
|
+
try {
|
|
6850
|
+
const raw = readFile(p2);
|
|
6851
|
+
const fm2 = matter2(raw).data;
|
|
6852
|
+
if (fm2.status === "done") {
|
|
6853
|
+
const rel = p2.replace(dir + "/", "").replace(dir + "\\", "").replace(/\\/g, "/");
|
|
6854
|
+
findings.push({
|
|
6855
|
+
file: rel,
|
|
6856
|
+
field: "status",
|
|
6857
|
+
issue: "inconsistent",
|
|
6858
|
+
detail: "Legacy status `done` detected. Canonical status is `completed`."
|
|
6859
|
+
});
|
|
6860
|
+
}
|
|
6861
|
+
} catch {
|
|
6862
|
+
}
|
|
6863
|
+
} else if (!isFile(p2) && !entry.startsWith(".")) {
|
|
6864
|
+
walkWIs(p2);
|
|
6865
|
+
}
|
|
6866
|
+
}
|
|
6867
|
+
};
|
|
6868
|
+
walkWIs(wiDir);
|
|
6869
|
+
}
|
|
6836
6870
|
return { findings, healthy, drifted };
|
|
6837
6871
|
}
|
|
6838
6872
|
|
|
6873
|
+
// src/core/cross-repo-evidence.ts
|
|
6874
|
+
function analyzeCrossRepoEvidence(input) {
|
|
6875
|
+
const findings = [];
|
|
6876
|
+
const { id, lifecycle, implementationStatus, validationStatus, releaseStatus, affectedModules } = input;
|
|
6877
|
+
const raw = input.rawFrontmatter;
|
|
6878
|
+
const evidence = raw.implementation_evidence;
|
|
6879
|
+
const repos = evidence?.repositories ?? {};
|
|
6880
|
+
const releaseGates = Array.isArray(raw.release_gates) ? raw.release_gates : [];
|
|
6881
|
+
const exceptions = Array.isArray(raw.completion_exceptions) ? raw.completion_exceptions : [];
|
|
6882
|
+
const repoEvidence = {};
|
|
6883
|
+
for (const [repoId, data] of Object.entries(repos)) {
|
|
6884
|
+
const d = data;
|
|
6885
|
+
repoEvidence[repoId] = {
|
|
6886
|
+
role: String(d.role ?? (repoId === "core" ? "core" : "module")),
|
|
6887
|
+
status: String(d.status ?? "unknown")
|
|
6888
|
+
};
|
|
6889
|
+
}
|
|
6890
|
+
for (const mod of affectedModules) {
|
|
6891
|
+
if (mod === "core") continue;
|
|
6892
|
+
if (!input.registeredModuleIds.includes(mod)) {
|
|
6893
|
+
findings.push({ id, severity: "blocking", message: `Affected module "${mod}" is not registered in .kaddo/modules.yml.` });
|
|
6894
|
+
}
|
|
6895
|
+
}
|
|
6896
|
+
for (const repoId of input.modifiedRepoIds) {
|
|
6897
|
+
if (!affectedModules.includes(repoId) && repoId !== "core") {
|
|
6898
|
+
findings.push({ id, severity: "blocking", message: `Repository "${repoId}" was modified but not declared in affected_modules.` });
|
|
6899
|
+
}
|
|
6900
|
+
}
|
|
6901
|
+
for (const mod of affectedModules) {
|
|
6902
|
+
if (!repoEvidence[mod] && Object.keys(repos).length > 0) {
|
|
6903
|
+
findings.push({ id, severity: "warning", message: `Declared affected module "${mod}" has no implementation evidence.` });
|
|
6904
|
+
}
|
|
6905
|
+
}
|
|
6906
|
+
for (const [repoId, data] of Object.entries(repos)) {
|
|
6907
|
+
const d = data;
|
|
6908
|
+
const validations = Array.isArray(d.validations) ? d.validations : [];
|
|
6909
|
+
for (const v of validations) {
|
|
6910
|
+
if (v.status === "not-run") {
|
|
6911
|
+
findings.push({ id, severity: "warning", message: `Validation "${v.command}" in ${repoId} was not executed.` });
|
|
6912
|
+
}
|
|
6913
|
+
}
|
|
6914
|
+
const migrations = Array.isArray(d.migrations) ? d.migrations : [];
|
|
6915
|
+
for (const m of migrations) {
|
|
6916
|
+
if (m.status === "blocked") {
|
|
6917
|
+
findings.push({ id, severity: "warning", message: `Migration "${m.id}" (${m.environment}) in ${repoId} is blocked.` });
|
|
6918
|
+
}
|
|
6919
|
+
}
|
|
6920
|
+
}
|
|
6921
|
+
if (lifecycle === "completed") {
|
|
6922
|
+
const proposedExceptions = exceptions.filter((e) => e.status === "proposed");
|
|
6923
|
+
if (proposedExceptions.length > 0) {
|
|
6924
|
+
findings.push({ id, severity: "blocking", message: "Work Item is completed but has proposed (not accepted) exceptions." });
|
|
6925
|
+
}
|
|
6926
|
+
if (releaseStatus === "ready") {
|
|
6927
|
+
const blockedGates = releaseGates.filter((g) => g.status === "blocked" || g.status === "failed");
|
|
6928
|
+
if (blockedGates.length > 0) {
|
|
6929
|
+
findings.push({ id, severity: "blocking", message: `Release status is "ready" but ${blockedGates.length} gate(s) are blocked/failed.` });
|
|
6930
|
+
}
|
|
6931
|
+
}
|
|
6932
|
+
}
|
|
6933
|
+
for (const [repoId, data] of Object.entries(repos)) {
|
|
6934
|
+
const d = data;
|
|
6935
|
+
const validations = Array.isArray(d.validations) ? d.validations : [];
|
|
6936
|
+
for (const v of validations) {
|
|
6937
|
+
if (v.status === "passed" && (!v.command || !v.command.trim())) {
|
|
6938
|
+
findings.push({ id, severity: "blocking", message: `Evidence in ${repoId} marked "passed" without a command record.` });
|
|
6939
|
+
}
|
|
6940
|
+
}
|
|
6941
|
+
}
|
|
6942
|
+
if (raw.status === "done") {
|
|
6943
|
+
findings.push({ id, severity: "warning", message: "Legacy status `done` detected. Canonical status is `completed`." });
|
|
6944
|
+
}
|
|
6945
|
+
if (raw.refined_by && raw.implemented_by && raw.refined_by === raw.implemented_by) {
|
|
6946
|
+
findings.push({ id, severity: "fyi", message: "refined_by and implemented_by point to the same agent." });
|
|
6947
|
+
}
|
|
6948
|
+
return {
|
|
6949
|
+
id,
|
|
6950
|
+
lifecycle,
|
|
6951
|
+
implementationStatus: implementationStatus || "not-started",
|
|
6952
|
+
validationStatus: validationStatus || "not-started",
|
|
6953
|
+
releaseStatus: releaseStatus || "not-assessed",
|
|
6954
|
+
affectedModules,
|
|
6955
|
+
findings,
|
|
6956
|
+
releaseGates,
|
|
6957
|
+
completionExceptions: exceptions,
|
|
6958
|
+
repoEvidence
|
|
6959
|
+
};
|
|
6960
|
+
}
|
|
6961
|
+
|
|
6839
6962
|
// src/commands/guard.ts
|
|
6840
6963
|
import path4 from "path";
|
|
6841
6964
|
import { parse as parseYaml7 } from "yaml";
|
|
@@ -7466,6 +7589,37 @@ async function runGuard(opts = {}) {
|
|
|
7466
7589
|
}
|
|
7467
7590
|
console.log("");
|
|
7468
7591
|
}
|
|
7592
|
+
const registeredModuleIds = loadMappedModules(dir).map((m) => m.id);
|
|
7593
|
+
const modifiedRepoIds = workspaceScan ? [...new Set(workspaceScan.changedFiles.map((c) => c.repoId))] : [];
|
|
7594
|
+
const wiArtifacts = artifacts.filter((a) => a.isWorkItem && a.affectedModules.length > 0);
|
|
7595
|
+
if (wiArtifacts.length > 0) {
|
|
7596
|
+
const evidenceSummaries = [];
|
|
7597
|
+
for (const wi of wiArtifacts) {
|
|
7598
|
+
const summary = analyzeCrossRepoEvidence({
|
|
7599
|
+
id: wi.id || wi.title,
|
|
7600
|
+
lifecycle: wi.lifecycle ?? "ready",
|
|
7601
|
+
implementationStatus: wi.implementationStatus,
|
|
7602
|
+
validationStatus: wi.validationStatus,
|
|
7603
|
+
releaseStatus: wi.releaseStatus,
|
|
7604
|
+
affectedModules: wi.affectedModules,
|
|
7605
|
+
rawFrontmatter: wi.rawFrontmatter,
|
|
7606
|
+
registeredModuleIds,
|
|
7607
|
+
modifiedRepoIds
|
|
7608
|
+
});
|
|
7609
|
+
if (summary.findings.length > 0) evidenceSummaries.push(summary);
|
|
7610
|
+
}
|
|
7611
|
+
if (evidenceSummaries.length > 0) {
|
|
7612
|
+
console.log("Cross-repo implementation evidence:");
|
|
7613
|
+
for (const s of evidenceSummaries) {
|
|
7614
|
+
console.log(` ${s.id}:`);
|
|
7615
|
+
for (const f of s.findings) {
|
|
7616
|
+
const icon = f.severity === "blocking" ? "\u2717" : f.severity === "warning" ? "!" : "\xB7";
|
|
7617
|
+
console.log(` ${icon} ${f.message}`);
|
|
7618
|
+
}
|
|
7619
|
+
}
|
|
7620
|
+
console.log("");
|
|
7621
|
+
}
|
|
7622
|
+
}
|
|
7469
7623
|
const ownerMap = loadOwners(dir);
|
|
7470
7624
|
const matchedDomains = collectMatchedDomains(activeMatches.map((m) => m.artifact.domains));
|
|
7471
7625
|
const affectedOwners = resolveAffectedOwners(matchedDomains, ownerMap);
|
|
@@ -7594,7 +7748,7 @@ function assessPhase(input) {
|
|
|
7594
7748
|
const techReady = !NOT_READY_LAYER.includes(techStatus);
|
|
7595
7749
|
if (techReady) {
|
|
7596
7750
|
return {
|
|
7597
|
-
phase: "
|
|
7751
|
+
phase: "Ready for Core Orchestration",
|
|
7598
7752
|
reasons: ["Module repo \u2014 Tech layer ready", "Business/Product managed by core"],
|
|
7599
7753
|
recommendedAgents: [],
|
|
7600
7754
|
nextStep: "Module knowledge is ready for core orchestration.",
|
|
@@ -10089,6 +10243,7 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
10089
10243
|
const selectedWIs = workItems.filter((a) => a.lifecycle && includedSet.has(a.lifecycle));
|
|
10090
10244
|
for (const wi of selectedWIs) {
|
|
10091
10245
|
const id = wi.id || wi.title;
|
|
10246
|
+
if (!id || !id.trim()) continue;
|
|
10092
10247
|
const wiNodeId = `wi:${id}`;
|
|
10093
10248
|
addNode({
|
|
10094
10249
|
id: wiNodeId,
|
|
@@ -10099,16 +10254,19 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
10099
10254
|
knowledge_level: wi.knowledgeLevel || void 0
|
|
10100
10255
|
});
|
|
10101
10256
|
for (const glob of wi.codeGlobs) {
|
|
10257
|
+
if (!glob || !glob.trim()) continue;
|
|
10102
10258
|
const codeId = `code:${glob}`;
|
|
10103
10259
|
addNode({ id: codeId, type: "code-glob", label: glob });
|
|
10104
10260
|
addEdge(wiNodeId, codeId, "owns");
|
|
10105
10261
|
}
|
|
10106
10262
|
for (const cap of wi.capabilities) {
|
|
10263
|
+
if (!cap || !cap.trim()) continue;
|
|
10107
10264
|
const capId = `capability:${slug(cap) || cap}`;
|
|
10108
10265
|
addNode({ id: capId, type: "capability", label: cap });
|
|
10109
10266
|
addEdge(wiNodeId, capId, "implements");
|
|
10110
10267
|
}
|
|
10111
10268
|
for (const dec of wi.decisions) {
|
|
10269
|
+
if (!dec || !dec.trim()) continue;
|
|
10112
10270
|
const adrId = `adr:${dec}`;
|
|
10113
10271
|
addNode({ id: adrId, type: "decision", label: dec });
|
|
10114
10272
|
addEdge(wiNodeId, adrId, "depends_on");
|
|
@@ -10125,16 +10283,19 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
10125
10283
|
}
|
|
10126
10284
|
}
|
|
10127
10285
|
for (const adr of all.filter(isAdr)) {
|
|
10128
|
-
const
|
|
10286
|
+
const adrLabel = adr.id || adr.title;
|
|
10287
|
+
if (!adrLabel || !adrLabel.trim()) continue;
|
|
10288
|
+
const adrId = `adr:${adrLabel}`;
|
|
10129
10289
|
const referenced = nodes.has(adrId);
|
|
10130
10290
|
if (scope === "all" || referenced) {
|
|
10131
10291
|
nodes.set(adrId, {
|
|
10132
10292
|
id: adrId,
|
|
10133
10293
|
type: "decision",
|
|
10134
|
-
label: `${adr.id} ${adr.title}`.trim() ||
|
|
10294
|
+
label: `${adr.id} ${adr.title}`.trim() || adrLabel,
|
|
10135
10295
|
path: adr.relPath
|
|
10136
10296
|
});
|
|
10137
10297
|
for (const glob of adr.codeGlobs) {
|
|
10298
|
+
if (!glob || !glob.trim()) continue;
|
|
10138
10299
|
const codeId = `code:${glob}`;
|
|
10139
10300
|
addNode({ id: codeId, type: "code-glob", label: glob });
|
|
10140
10301
|
addEdge(adrId, codeId, "governs");
|
|
@@ -10190,13 +10351,20 @@ function renderGraphMermaid(graph) {
|
|
|
10190
10351
|
safeIds.set(id, candidate);
|
|
10191
10352
|
return candidate;
|
|
10192
10353
|
};
|
|
10193
|
-
const escapeLabel = (s) => s.replace(/"/g, "'");
|
|
10354
|
+
const escapeLabel = (s) => s.replace(/"/g, "'").replace(/\[/g, "(").replace(/\]/g, ")").replace(/\n/g, " ");
|
|
10355
|
+
const validNodes = graph.nodes.filter(
|
|
10356
|
+
(n) => n.id && n.id.trim() !== "" && n.label && n.label.trim() !== ""
|
|
10357
|
+
);
|
|
10358
|
+
const validNodeIds = new Set(validNodes.map((n) => n.id));
|
|
10194
10359
|
const lines = ["flowchart LR"];
|
|
10195
|
-
for (const node of
|
|
10360
|
+
for (const node of validNodes) {
|
|
10196
10361
|
lines.push(` ${safe(node.id)}["${escapeLabel(node.label)}"]`);
|
|
10197
10362
|
}
|
|
10198
|
-
|
|
10199
|
-
|
|
10363
|
+
const validEdges = graph.edges.filter(
|
|
10364
|
+
(e) => validNodeIds.has(e.from) && validNodeIds.has(e.to)
|
|
10365
|
+
);
|
|
10366
|
+
if (validEdges.length > 0) lines.push("");
|
|
10367
|
+
for (const edge of validEdges) {
|
|
10200
10368
|
lines.push(` ${safe(edge.from)} -->|${edge.type}| ${safe(edge.to)}`);
|
|
10201
10369
|
}
|
|
10202
10370
|
return lines.join("\n") + "\n";
|
|
@@ -11921,6 +12089,47 @@ function buildReadinessReport(dir, now = /* @__PURE__ */ new Date()) {
|
|
|
11921
12089
|
if (!config) return stub("not-initialized", rec.label, rec.command);
|
|
11922
12090
|
if (config.project.state === "new") return stub("not-applicable", "This project uses the standard new-project Kaddo flow.");
|
|
11923
12091
|
if (config.project.state === "legacy") return stub("legacy-project", "Use the legacy project flow.");
|
|
12092
|
+
if (isModule(config)) {
|
|
12093
|
+
const scan3 = exists(join(dir, ".kaddo", "scan.json")) ? "available" : "missing";
|
|
12094
|
+
const understand2 = exists(join(dir, ".kaddo", "understand.md")) ? "available" : "missing";
|
|
12095
|
+
const mcNew = "knowledge/tech/module/module-context.md";
|
|
12096
|
+
const mcLegacy = "knowledge/module/module-context.md";
|
|
12097
|
+
const mcPath = exists(join(dir, mcNew)) ? mcNew : mcLegacy;
|
|
12098
|
+
const qMC = analyzeKnowledgeArtifact(dir, mcPath);
|
|
12099
|
+
const qCS = analyzeKnowledgeArtifact(dir, "knowledge/tech/current-state.md");
|
|
12100
|
+
const qCB = analyzeKnowledgeArtifact(dir, "knowledge/tech/codebase.md");
|
|
12101
|
+
const moduleBaseline = qMC !== "missing" && qCS !== "missing" && qCB !== "missing" ? "complete" : "incomplete";
|
|
12102
|
+
const moduleReady = qMC === "useful" && qCS === "useful" && qCB === "useful";
|
|
12103
|
+
const overall2 = moduleReady ? "ready-for-core-orchestration" : moduleBaseline === "incomplete" ? "bootstrap-incomplete" : "knowledge-incomplete";
|
|
12104
|
+
return {
|
|
12105
|
+
project_name: config.project.name,
|
|
12106
|
+
project_type: config.project.state,
|
|
12107
|
+
project_role: "module",
|
|
12108
|
+
overall: overall2,
|
|
12109
|
+
signals: {
|
|
12110
|
+
scan: scan3,
|
|
12111
|
+
understand: understand2,
|
|
12112
|
+
bootstrap_baseline: moduleBaseline,
|
|
12113
|
+
agents: "managed-by-core",
|
|
12114
|
+
skills: "managed-by-core",
|
|
12115
|
+
current_state: qCS,
|
|
12116
|
+
codebase: qCB,
|
|
12117
|
+
module_context: qMC,
|
|
12118
|
+
capabilities: "managed-by-core",
|
|
12119
|
+
product: "not-applicable",
|
|
12120
|
+
business: "not-applicable",
|
|
12121
|
+
roadmap: "managed-by-core",
|
|
12122
|
+
work_items: "managed-by-core",
|
|
12123
|
+
adapters: [],
|
|
12124
|
+
blocking_open_questions: 0,
|
|
12125
|
+
assumed_questions: 0,
|
|
12126
|
+
resolved_questions: 0,
|
|
12127
|
+
deferred_questions: 0
|
|
12128
|
+
},
|
|
12129
|
+
recommended_next_step: { label: rec.label, ...rec.command ? { command: rec.command } : {} },
|
|
12130
|
+
nextStepRecommendation: rec
|
|
12131
|
+
};
|
|
12132
|
+
}
|
|
11924
12133
|
const scan2 = exists(join(dir, ".kaddo", "scan.json")) ? "available" : "missing";
|
|
11925
12134
|
const understand = exists(join(dir, ".kaddo", "understand.md")) ? "available" : "missing";
|
|
11926
12135
|
const presence = Object.fromEntries(KNOWLEDGE_FILES2.map((f) => [f.key, analyzeKnowledgeArtifact(dir, f.path)]));
|
|
@@ -12891,51 +13100,73 @@ function buildProjectExplanation(dir) {
|
|
|
12891
13100
|
const roadmapMd = exists(roadmapPath) ? readFile(roadmapPath) : null;
|
|
12892
13101
|
const roadmap = roadmapStats(roadmapMd, items.length);
|
|
12893
13102
|
const mappedModules = loadMappedModules(dir);
|
|
13103
|
+
const moduleRepo = config != null && isModule(config);
|
|
12894
13104
|
const missingKnowledge = [];
|
|
12895
|
-
if (
|
|
12896
|
-
|
|
12897
|
-
|
|
12898
|
-
|
|
12899
|
-
if (!knowledge.hasArchitecture) missingKnowledge.push("Tech knowledge (knowledge/tech/)");
|
|
12900
|
-
if (!knowledge.hasRoadmap) missingKnowledge.push("Roadmap (knowledge/delivery/roadmap.md)");
|
|
12901
|
-
if (!knowledge.hasAgents) missingKnowledge.push("Agents (knowledge/agents/)");
|
|
12902
|
-
if (items.length === 0) missingKnowledge.push("Work items (knowledge/delivery/work-items/)");
|
|
12903
|
-
const suggestedNextSteps = [];
|
|
12904
|
-
const baselineIncomplete = !knowledge.hasBusiness || !knowledge.hasProduct;
|
|
12905
|
-
if (baselineIncomplete) {
|
|
12906
|
-
suggestedNextSteps.push("Run `kaddo bootstrap` to create the project knowledge baseline.");
|
|
12907
|
-
if (!knowledge.hasAgents) suggestedNextSteps.push("Then run `kaddo add agents`.");
|
|
12908
|
-
if (!knowledge.hasSkills) suggestedNextSteps.push("Then run `kaddo add skills`.");
|
|
12909
|
-
suggestedNextSteps.push("Then run `kaddo context`.");
|
|
12910
|
-
suggestedNextSteps.push("Then run `kaddo understand`.");
|
|
13105
|
+
if (moduleRepo) {
|
|
13106
|
+
if (!knowledge.hasScan) missingKnowledge.push("Scan baseline (.kaddo/scan.json)");
|
|
13107
|
+
if (!knowledge.hasContextPack) missingKnowledge.push("Context pack (.kaddo/context-pack.md)");
|
|
13108
|
+
if (!knowledge.hasArchitecture) missingKnowledge.push("Tech knowledge (knowledge/tech/)");
|
|
12911
13109
|
} else {
|
|
13110
|
+
if (!knowledge.hasScan) missingKnowledge.push("Scan baseline (.kaddo/scan.json)");
|
|
13111
|
+
if (!knowledge.hasContextPack) missingKnowledge.push("Context pack (.kaddo/context-pack.md)");
|
|
13112
|
+
if (!knowledge.hasInventory) missingKnowledge.push("Inventory (knowledge/inventory.md)");
|
|
13113
|
+
if (!knowledge.hasCapabilities) missingKnowledge.push("Product knowledge (knowledge/product/)");
|
|
13114
|
+
if (!knowledge.hasArchitecture) missingKnowledge.push("Tech knowledge (knowledge/tech/)");
|
|
13115
|
+
if (!knowledge.hasRoadmap) missingKnowledge.push("Roadmap (knowledge/delivery/roadmap.md)");
|
|
13116
|
+
if (!knowledge.hasAgents) missingKnowledge.push("Agents (knowledge/agents/)");
|
|
13117
|
+
if (items.length === 0) missingKnowledge.push("Work items (knowledge/delivery/work-items/)");
|
|
13118
|
+
}
|
|
13119
|
+
const suggestedNextSteps = [];
|
|
13120
|
+
if (moduleRepo) {
|
|
12912
13121
|
if (!knowledge.hasScan) {
|
|
12913
13122
|
suggestedNextSteps.push("Run `kaddo scan` to detect the technical stack.");
|
|
12914
13123
|
} else if (!knowledge.hasContextPack) {
|
|
12915
13124
|
suggestedNextSteps.push("Run `kaddo context` to prepare an LLM context pack.");
|
|
13125
|
+
} else if (!knowledge.hasArchitecture) {
|
|
13126
|
+
suggestedNextSteps.push("Use module-context-agent to refine knowledge/tech/module/module-context.md.");
|
|
13127
|
+
} else {
|
|
13128
|
+
suggestedNextSteps.push("Return to the core repository.");
|
|
13129
|
+
suggestedNextSteps.push("Create or continue the Work Item from the core.");
|
|
13130
|
+
suggestedNextSteps.push("Add this module to `affected_modules` when the change touches it.");
|
|
13131
|
+
suggestedNextSteps.push("Re-run `kaddo context` and `kaddo understand` from the core.");
|
|
12916
13132
|
}
|
|
12917
|
-
|
|
12918
|
-
|
|
12919
|
-
|
|
12920
|
-
|
|
12921
|
-
suggestedNextSteps.push("
|
|
12922
|
-
|
|
12923
|
-
|
|
12924
|
-
suggestedNextSteps.push("
|
|
12925
|
-
}
|
|
12926
|
-
|
|
12927
|
-
|
|
12928
|
-
|
|
12929
|
-
|
|
12930
|
-
|
|
12931
|
-
)
|
|
12932
|
-
|
|
12933
|
-
|
|
12934
|
-
|
|
12935
|
-
|
|
12936
|
-
|
|
12937
|
-
|
|
12938
|
-
|
|
13133
|
+
} else {
|
|
13134
|
+
const baselineIncomplete = !knowledge.hasBusiness || !knowledge.hasProduct;
|
|
13135
|
+
if (baselineIncomplete) {
|
|
13136
|
+
suggestedNextSteps.push("Run `kaddo bootstrap` to create the project knowledge baseline.");
|
|
13137
|
+
if (!knowledge.hasAgents) suggestedNextSteps.push("Then run `kaddo add agents`.");
|
|
13138
|
+
if (!knowledge.hasSkills) suggestedNextSteps.push("Then run `kaddo add skills`.");
|
|
13139
|
+
suggestedNextSteps.push("Then run `kaddo context`.");
|
|
13140
|
+
suggestedNextSteps.push("Then run `kaddo understand`.");
|
|
13141
|
+
} else {
|
|
13142
|
+
if (!knowledge.hasScan) {
|
|
13143
|
+
suggestedNextSteps.push("Run `kaddo scan` to detect the technical stack.");
|
|
13144
|
+
} else if (!knowledge.hasContextPack) {
|
|
13145
|
+
suggestedNextSteps.push("Run `kaddo context` to prepare an LLM context pack.");
|
|
13146
|
+
}
|
|
13147
|
+
if (!knowledge.hasAgents) {
|
|
13148
|
+
suggestedNextSteps.push("Run `kaddo add agents` to install knowledge agents.");
|
|
13149
|
+
}
|
|
13150
|
+
if (!knowledge.hasCapabilities) {
|
|
13151
|
+
suggestedNextSteps.push("Use capability-agent to generate knowledge/product/capabilities.md.");
|
|
13152
|
+
}
|
|
13153
|
+
if (!knowledge.hasArchitecture) {
|
|
13154
|
+
suggestedNextSteps.push("Use architecture-agent to generate knowledge/tech/current-state.md.");
|
|
13155
|
+
}
|
|
13156
|
+
if (!knowledge.hasRoadmap) {
|
|
13157
|
+
suggestedNextSteps.push("Use roadmap-agent to generate knowledge/delivery/roadmap.md.");
|
|
13158
|
+
} else if (roadmap.remaining > 0) {
|
|
13159
|
+
suggestedNextSteps.push(
|
|
13160
|
+
`Materialize ${roadmap.remaining} roadmap candidate(s) with \`kaddo create --from roadmap\`.`
|
|
13161
|
+
);
|
|
13162
|
+
}
|
|
13163
|
+
if (items.length === 0 && !roadmap.present) {
|
|
13164
|
+
suggestedNextSteps.push("Create your first Work Item with `kaddo create`.");
|
|
13165
|
+
} else if (ownership.workItemsMissingOwnership > 0) {
|
|
13166
|
+
suggestedNextSteps.push(
|
|
13167
|
+
"Run `kaddo owners suggest` for Work Items without code ownership."
|
|
13168
|
+
);
|
|
13169
|
+
}
|
|
12939
13170
|
}
|
|
12940
13171
|
}
|
|
12941
13172
|
const readiness = buildReadinessReport(dir);
|
|
@@ -12978,7 +13209,30 @@ function buildProjectExplanation(dir) {
|
|
|
12978
13209
|
projectRoute: buildProjectRoute(dir),
|
|
12979
13210
|
scanSignals: loadScanSignals(dir),
|
|
12980
13211
|
metadataHealth: analyzeMetadataHealth(dir),
|
|
12981
|
-
isModuleRepo: config != null && isModule(config)
|
|
13212
|
+
isModuleRepo: config != null && isModule(config),
|
|
13213
|
+
implementationEvidence: workItemArtifacts.filter((a) => a.affectedModules.length > 0).map((a) => {
|
|
13214
|
+
const fm2 = a.rawFrontmatter;
|
|
13215
|
+
const evidence = fm2.implementation_evidence;
|
|
13216
|
+
const repos = evidence?.repositories ?? {};
|
|
13217
|
+
const repoEvidence = {};
|
|
13218
|
+
for (const [repoId, data] of Object.entries(repos)) {
|
|
13219
|
+
repoEvidence[repoId] = {
|
|
13220
|
+
role: String(data.role ?? (repoId === "core" ? "core" : "module")),
|
|
13221
|
+
status: String(data.status ?? "unknown")
|
|
13222
|
+
};
|
|
13223
|
+
}
|
|
13224
|
+
return {
|
|
13225
|
+
id: a.id || a.title,
|
|
13226
|
+
lifecycle: lifecycleStateOf({ status: a.status, filePath: a.filePath }),
|
|
13227
|
+
implementationStatus: a.implementationStatus || "not-started",
|
|
13228
|
+
validationStatus: a.validationStatus || "not-started",
|
|
13229
|
+
releaseStatus: a.releaseStatus || "not-assessed",
|
|
13230
|
+
affectedModules: a.affectedModules,
|
|
13231
|
+
releaseGates: Array.isArray(fm2.release_gates) ? fm2.release_gates : [],
|
|
13232
|
+
completionExceptions: Array.isArray(fm2.completion_exceptions) ? fm2.completion_exceptions : [],
|
|
13233
|
+
repoEvidence
|
|
13234
|
+
};
|
|
13235
|
+
})
|
|
12982
13236
|
};
|
|
12983
13237
|
}
|
|
12984
13238
|
function stateLabel(state) {
|
|
@@ -13031,11 +13285,19 @@ function renderExplanationHuman(exp) {
|
|
|
13031
13285
|
lines.push("## Knowledge Status");
|
|
13032
13286
|
lines.push(`- Inventory: ${exp.knowledge.hasInventory ? "available" : "missing"}`);
|
|
13033
13287
|
lines.push(`- Context pack: ${exp.knowledge.hasContextPack ? "available" : "missing"}`);
|
|
13034
|
-
|
|
13035
|
-
|
|
13036
|
-
|
|
13037
|
-
|
|
13038
|
-
|
|
13288
|
+
if (exp.isModuleRepo) {
|
|
13289
|
+
lines.push("- Business: Not applicable");
|
|
13290
|
+
lines.push("- Product: Not applicable");
|
|
13291
|
+
lines.push(`- Tech: ${ls("Tech")}`);
|
|
13292
|
+
lines.push("- Delivery: Managed by core");
|
|
13293
|
+
lines.push("- Agents: Managed by core");
|
|
13294
|
+
} else {
|
|
13295
|
+
lines.push(`- Business: ${ls("Business")}`);
|
|
13296
|
+
lines.push(`- Product: ${ls("Product")}`);
|
|
13297
|
+
lines.push(`- Tech: ${ls("Tech")}`);
|
|
13298
|
+
lines.push(`- Delivery: ${ls("Delivery")}`);
|
|
13299
|
+
lines.push(`- Agents: ${exp.knowledge.hasAgents ? "available" : "missing"}`);
|
|
13300
|
+
}
|
|
13039
13301
|
if (exp.roadmap.present) {
|
|
13040
13302
|
lines.push(`- Roadmap initiatives: ${exp.roadmap.initiatives}`);
|
|
13041
13303
|
lines.push(`- Work Item candidates: ${exp.roadmap.work_item_candidates}`);
|
|
@@ -13096,6 +13358,36 @@ function renderExplanationHuman(exp) {
|
|
|
13096
13358
|
}
|
|
13097
13359
|
lines.push("");
|
|
13098
13360
|
}
|
|
13361
|
+
if (exp.implementationEvidence.length > 0) {
|
|
13362
|
+
lines.push("## Implementation Evidence");
|
|
13363
|
+
for (const ev of exp.implementationEvidence) {
|
|
13364
|
+
lines.push("");
|
|
13365
|
+
lines.push(`### ${ev.id}`);
|
|
13366
|
+
lines.push(`- Lifecycle: ${ev.lifecycle.charAt(0).toUpperCase() + ev.lifecycle.slice(1)}`);
|
|
13367
|
+
lines.push(`- Implementation: ${ev.implementationStatus}`);
|
|
13368
|
+
lines.push(`- Validation: ${ev.validationStatus}`);
|
|
13369
|
+
lines.push(`- Release: ${ev.releaseStatus}`);
|
|
13370
|
+
if (Object.keys(ev.repoEvidence).length > 0) {
|
|
13371
|
+
lines.push("Repositories:");
|
|
13372
|
+
for (const [repoId, info] of Object.entries(ev.repoEvidence)) {
|
|
13373
|
+
lines.push(`- ${repoId} \u2014 ${info.status}`);
|
|
13374
|
+
}
|
|
13375
|
+
} else if (ev.affectedModules.length > 0) {
|
|
13376
|
+
lines.push(`Affected modules: ${ev.affectedModules.join(", ")}`);
|
|
13377
|
+
}
|
|
13378
|
+
const pendingGates = ev.releaseGates.filter((g) => g.status === "blocked" || g.status === "pending");
|
|
13379
|
+
if (pendingGates.length > 0) {
|
|
13380
|
+
lines.push("Pending release gates:");
|
|
13381
|
+
for (const g of pendingGates) lines.push(`- ${g.id}${g.reason ? ` \u2014 ${g.reason}` : ""}`);
|
|
13382
|
+
}
|
|
13383
|
+
const acceptedExceptions = ev.completionExceptions.filter((e) => e.status === "accepted" || e.status === "deferred");
|
|
13384
|
+
if (acceptedExceptions.length > 0) {
|
|
13385
|
+
lines.push("Completion exceptions:");
|
|
13386
|
+
for (const e of acceptedExceptions) lines.push(`- ${e.id}: ${e.status}${e.reason ? ` \u2014 ${e.reason}` : ""}`);
|
|
13387
|
+
}
|
|
13388
|
+
}
|
|
13389
|
+
lines.push("");
|
|
13390
|
+
}
|
|
13099
13391
|
if (exp.domains.length > 0) {
|
|
13100
13392
|
lines.push("## Domains");
|
|
13101
13393
|
lines.push(`- ${exp.domains.join(", ")}`);
|
|
@@ -13196,14 +13488,27 @@ function renderExplanationHuman(exp) {
|
|
|
13196
13488
|
lines.push(`- understand: ${s.understand}`);
|
|
13197
13489
|
lines.push(`- agents: ${s.agents}`);
|
|
13198
13490
|
lines.push(`- skills: ${s.skills}`);
|
|
13199
|
-
|
|
13200
|
-
|
|
13491
|
+
if (exp.isModuleRepo) {
|
|
13492
|
+
lines.push("- business: not-applicable");
|
|
13493
|
+
lines.push("- product: not-applicable");
|
|
13494
|
+
} else {
|
|
13495
|
+
lines.push(`- business: ${s.business}`);
|
|
13496
|
+
lines.push(`- product: ${s.product}`);
|
|
13497
|
+
}
|
|
13201
13498
|
lines.push(`- capabilities: ${s.capabilities}`);
|
|
13202
13499
|
lines.push(`- current-state: ${s.current_state}`);
|
|
13203
13500
|
lines.push(`- codebase: ${s.codebase}`);
|
|
13204
|
-
|
|
13205
|
-
|
|
13206
|
-
|
|
13501
|
+
if (s.module_context != null) {
|
|
13502
|
+
lines.push(`- module-context: ${s.module_context}`);
|
|
13503
|
+
}
|
|
13504
|
+
if (!exp.isModuleRepo) {
|
|
13505
|
+
lines.push(`- roadmap: ${s.roadmap}`);
|
|
13506
|
+
lines.push(`- work-items: ${s.work_items}`);
|
|
13507
|
+
lines.push(`- adapters: ${s.adapters.length > 0 ? s.adapters.join(", ") + " installed" : "none installed"}`);
|
|
13508
|
+
} else {
|
|
13509
|
+
lines.push("- roadmap: managed-by-core");
|
|
13510
|
+
lines.push("- work-items: managed-by-core");
|
|
13511
|
+
}
|
|
13207
13512
|
lines.push(`- blocking open questions: ${s.blocking_open_questions}`);
|
|
13208
13513
|
lines.push(`- assumptions: ${s.assumed_questions}`);
|
|
13209
13514
|
lines.push(`- deferred: ${s.deferred_questions}`);
|
|
@@ -13540,7 +13845,7 @@ var OPERATING_RULES = [
|
|
|
13540
13845
|
"Kaddo itself never calls an LLM and never runs git \u2014 every git action is the human\u2019s."
|
|
13541
13846
|
];
|
|
13542
13847
|
function toContextWorkItem(a) {
|
|
13543
|
-
|
|
13848
|
+
const wi = {
|
|
13544
13849
|
id: a.id,
|
|
13545
13850
|
type: a.type,
|
|
13546
13851
|
title: a.title,
|
|
@@ -13550,6 +13855,11 @@ function toContextWorkItem(a) {
|
|
|
13550
13855
|
domains: a.domains,
|
|
13551
13856
|
source: parseWorkItemSource(a.rawFrontmatter)
|
|
13552
13857
|
};
|
|
13858
|
+
if (a.implementationStatus) wi.implementationStatus = a.implementationStatus;
|
|
13859
|
+
if (a.validationStatus) wi.validationStatus = a.validationStatus;
|
|
13860
|
+
if (a.releaseStatus) wi.releaseStatus = a.releaseStatus;
|
|
13861
|
+
if (a.affectedModules.length > 0) wi.affectedModules = a.affectedModules;
|
|
13862
|
+
return wi;
|
|
13553
13863
|
}
|
|
13554
13864
|
function toContextArtifact(a) {
|
|
13555
13865
|
return { id: a.id, type: a.type, title: a.title, summary: a.summary, codeGlobs: a.codeGlobs };
|
|
@@ -13568,11 +13878,11 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
|
|
|
13568
13878
|
if (!inventoryAvailable) {
|
|
13569
13879
|
missing.push("No technical inventory found. Run `kaddo scan` to generate it.");
|
|
13570
13880
|
}
|
|
13881
|
+
const moduleRepo = config != null && isModule(config);
|
|
13571
13882
|
const knowledgeSummary = readMarkdownSummary(dir, "knowledge.md") ?? "";
|
|
13572
|
-
if (!knowledgeSummary) {
|
|
13883
|
+
if (!knowledgeSummary && !moduleRepo) {
|
|
13573
13884
|
missing.push("No project knowledge summary found yet.");
|
|
13574
13885
|
}
|
|
13575
|
-
const moduleRepo = config != null && isModule(config);
|
|
13576
13886
|
const roadmapSummary = readMarkdownSummary(dir, "delivery/roadmap.md") ?? "";
|
|
13577
13887
|
if (!roadmapSummary && !moduleRepo) {
|
|
13578
13888
|
missing.push("No roadmap baseline found.");
|
|
@@ -13746,11 +14056,12 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
|
|
|
13746
14056
|
}
|
|
13747
14057
|
return contexts;
|
|
13748
14058
|
})(),
|
|
14059
|
+
isModuleRepo: moduleRepo,
|
|
13749
14060
|
missing,
|
|
13750
14061
|
// VS-052/VS-073.2: the handoff is driven by the unified next step, so the pack never contradicts
|
|
13751
14062
|
// the Current Phase block above it.
|
|
13752
14063
|
handoff: {
|
|
13753
|
-
recommendedAgents: isBootstrap ? [] : unifiedPhase.recommendedAgents.length > 0 ? unifiedPhase.recommendedAgents : recommendedAgentsForState(state),
|
|
14064
|
+
recommendedAgents: isBootstrap ? [] : unifiedPhase.recommendedAgents.length > 0 ? unifiedPhase.recommendedAgents : moduleRepo ? [] : recommendedAgentsForState(state),
|
|
13754
14065
|
nextSteps: [nextStepRecommendation.label],
|
|
13755
14066
|
instructions: isBootstrap ? ["No agent handoff yet.", "Run `kaddo bootstrap` first to create the baseline files."] : unifiedPhase.llmInstructions.length > 0 ? unifiedPhase.llmInstructions : LLM_INSTRUCTIONS,
|
|
13756
14067
|
operatingRules: OPERATING_RULES
|
|
@@ -14061,7 +14372,11 @@ function renderContextPack(pack) {
|
|
|
14061
14372
|
}
|
|
14062
14373
|
}
|
|
14063
14374
|
parts.push("## Recommended Agent Handoff\n");
|
|
14064
|
-
if (
|
|
14375
|
+
if (pack.isModuleRepo && handoff.recommendedAgents.length === 0) {
|
|
14376
|
+
parts.push("No local agent action is required.\n");
|
|
14377
|
+
parts.push("This module is ready for core orchestration.\n");
|
|
14378
|
+
parts.push("Business, Product, Delivery, Agents and Skills are managed by the core repository.\n");
|
|
14379
|
+
} else if (isBootstrapIncomplete) {
|
|
14065
14380
|
parts.push("No agent handoff yet.\n");
|
|
14066
14381
|
parts.push("Run `kaddo bootstrap` first to create the baseline files.\n");
|
|
14067
14382
|
} else if (rec.agent && rec.agentInstalled === false) {
|
|
@@ -15053,7 +15368,7 @@ function findWorkItemFile(dir, id) {
|
|
|
15053
15368
|
function updateWorkItemFile(filePath, learning) {
|
|
15054
15369
|
const raw = readFile(filePath);
|
|
15055
15370
|
const { data, content } = matter8(raw);
|
|
15056
|
-
data.status = "
|
|
15371
|
+
data.status = "completed";
|
|
15057
15372
|
data.completed_at = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
15058
15373
|
let updatedContent = content;
|
|
15059
15374
|
if (content.includes("_What did we learn from this change? Update after completion._")) {
|
|
@@ -15088,24 +15403,24 @@ async function runLearn(artifactId) {
|
|
|
15088
15403
|
}
|
|
15089
15404
|
intro2("kaddo learn");
|
|
15090
15405
|
const artifacts = readArtifacts(join(dir, ARCH_DIR9));
|
|
15091
|
-
const
|
|
15092
|
-
(a) => a.status === "in-progress" && a.type !== "current-state" && a.type !== "roadmap"
|
|
15406
|
+
const closable = artifacts.filter(
|
|
15407
|
+
(a) => (a.status === "in-progress" || a.status === "completed" || a.status === "done") && a.type !== "current-state" && a.type !== "roadmap"
|
|
15093
15408
|
);
|
|
15094
|
-
if (
|
|
15095
|
-
log2.warn("No in-progress work items found.");
|
|
15409
|
+
if (closable.length === 0) {
|
|
15410
|
+
log2.warn("No in-progress or completed work items found.");
|
|
15096
15411
|
outro2("Nothing to close.");
|
|
15097
15412
|
return;
|
|
15098
15413
|
}
|
|
15099
15414
|
let targetId;
|
|
15100
15415
|
if (artifactId) {
|
|
15101
15416
|
targetId = artifactId;
|
|
15102
|
-
} else if (
|
|
15103
|
-
targetId =
|
|
15104
|
-
log2.info(`Closing: ${targetId} \u2014 ${
|
|
15417
|
+
} else if (closable.length === 1) {
|
|
15418
|
+
targetId = closable[0].id || closable[0].title;
|
|
15419
|
+
log2.info(`Closing: ${targetId} \u2014 ${closable[0].summary || closable[0].title}`);
|
|
15105
15420
|
} else {
|
|
15106
15421
|
const chosen = await select2({
|
|
15107
15422
|
message: "Which work item are you closing?",
|
|
15108
|
-
options:
|
|
15423
|
+
options: closable.map((a) => ({
|
|
15109
15424
|
value: a.id || a.title,
|
|
15110
15425
|
label: `${a.id || a.title} \u2014 ${a.summary || a.title}`
|
|
15111
15426
|
}))
|
|
@@ -15122,9 +15437,26 @@ async function runLearn(artifactId) {
|
|
|
15122
15437
|
placeholder: "e.g. The retry logic needed a separate queue to avoid blocking the main flow",
|
|
15123
15438
|
validate: (v) => v.trim().length === 0 ? "Learning is required." : void 0
|
|
15124
15439
|
});
|
|
15125
|
-
|
|
15126
|
-
|
|
15440
|
+
const wiRaw = readFile(filePath);
|
|
15441
|
+
const wiData = matter8(wiRaw).data;
|
|
15442
|
+
const hasExceptions = wiData.validation_status === "accepted-with-exceptions" || Array.isArray(wiData.completion_exceptions) && wiData.completion_exceptions.length > 0;
|
|
15443
|
+
const releaseBlocked = wiData.release_status === "blocked";
|
|
15444
|
+
let enrichedLearning = learning.trim();
|
|
15445
|
+
if (hasExceptions || releaseBlocked) {
|
|
15446
|
+
const notes = [];
|
|
15447
|
+
if (hasExceptions) notes.push("Validation exceptions were accepted for this Work Item.");
|
|
15448
|
+
if (releaseBlocked) {
|
|
15449
|
+
const gates = Array.isArray(wiData.release_gates) ? wiData.release_gates.filter((g) => g.status === "blocked" || g.status === "pending").map((g) => g.id) : [];
|
|
15450
|
+
notes.push(gates.length > 0 ? `Release gates remain: ${gates.join(", ")}.` : "Production release remains blocked.");
|
|
15451
|
+
}
|
|
15452
|
+
enrichedLearning += "\n\n> " + notes.join(" ");
|
|
15453
|
+
}
|
|
15454
|
+
updateWorkItemFile(filePath, enrichedLearning);
|
|
15455
|
+
log2.success(`${targetId} marked as completed`);
|
|
15127
15456
|
log2.success(`Learning recorded in ${filePath.replace(dir + "/", "")}`);
|
|
15457
|
+
if (hasExceptions) {
|
|
15458
|
+
log2.warn("Learning captured from a Work Item completed with validation exceptions.");
|
|
15459
|
+
}
|
|
15128
15460
|
log2.info("Consider updating knowledge/knowledge.md if this changes the current state.");
|
|
15129
15461
|
outro2("Work item closed.");
|
|
15130
15462
|
}
|