@kaddo/cli 3.65.0 → 3.67.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 +372 -25
- 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,135 @@ 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 (validationStatus === "accepted-with-exceptions") {
|
|
6943
|
+
const hasExceptions = exceptions.length > 0;
|
|
6944
|
+
const hasValidationGate = releaseGates.some(
|
|
6945
|
+
(g) => (g.status === "pending" || g.status === "blocked") && (g.id.includes("validation") || g.id.includes("test"))
|
|
6946
|
+
);
|
|
6947
|
+
if (!hasExceptions && !hasValidationGate) {
|
|
6948
|
+
findings.push({ id, severity: "warning", message: "Validation status is accepted-with-exceptions, but no exception evidence was found." });
|
|
6949
|
+
}
|
|
6950
|
+
}
|
|
6951
|
+
if (raw.status === "done") {
|
|
6952
|
+
findings.push({ id, severity: "warning", message: "Legacy status `done` detected. Canonical status is `completed`." });
|
|
6953
|
+
}
|
|
6954
|
+
if (raw.refined_by && raw.implemented_by && raw.refined_by === raw.implemented_by) {
|
|
6955
|
+
findings.push({ id, severity: "fyi", message: "refined_by and implemented_by point to the same agent." });
|
|
6956
|
+
}
|
|
6957
|
+
const historicalDefault = lifecycle === "completed" || lifecycle === "archived" ? "not-assessed" : "not-started";
|
|
6958
|
+
return {
|
|
6959
|
+
id,
|
|
6960
|
+
lifecycle,
|
|
6961
|
+
implementationStatus: implementationStatus || historicalDefault,
|
|
6962
|
+
validationStatus: validationStatus || historicalDefault,
|
|
6963
|
+
releaseStatus: releaseStatus || "not-assessed",
|
|
6964
|
+
affectedModules,
|
|
6965
|
+
findings,
|
|
6966
|
+
releaseGates,
|
|
6967
|
+
completionExceptions: exceptions,
|
|
6968
|
+
repoEvidence
|
|
6969
|
+
};
|
|
6970
|
+
}
|
|
6971
|
+
|
|
6839
6972
|
// src/commands/guard.ts
|
|
6840
6973
|
import path4 from "path";
|
|
6841
6974
|
import { parse as parseYaml7 } from "yaml";
|
|
@@ -7466,6 +7599,37 @@ async function runGuard(opts = {}) {
|
|
|
7466
7599
|
}
|
|
7467
7600
|
console.log("");
|
|
7468
7601
|
}
|
|
7602
|
+
const registeredModuleIds = loadMappedModules(dir).map((m) => m.id);
|
|
7603
|
+
const modifiedRepoIds = workspaceScan ? [...new Set(workspaceScan.changedFiles.map((c) => c.repoId))] : [];
|
|
7604
|
+
const wiArtifacts = artifacts.filter((a) => a.isWorkItem && a.affectedModules.length > 0);
|
|
7605
|
+
if (wiArtifacts.length > 0) {
|
|
7606
|
+
const evidenceSummaries = [];
|
|
7607
|
+
for (const wi of wiArtifacts) {
|
|
7608
|
+
const summary = analyzeCrossRepoEvidence({
|
|
7609
|
+
id: wi.id || wi.title,
|
|
7610
|
+
lifecycle: wi.lifecycle ?? "ready",
|
|
7611
|
+
implementationStatus: wi.implementationStatus,
|
|
7612
|
+
validationStatus: wi.validationStatus,
|
|
7613
|
+
releaseStatus: wi.releaseStatus,
|
|
7614
|
+
affectedModules: wi.affectedModules,
|
|
7615
|
+
rawFrontmatter: wi.rawFrontmatter,
|
|
7616
|
+
registeredModuleIds,
|
|
7617
|
+
modifiedRepoIds
|
|
7618
|
+
});
|
|
7619
|
+
if (summary.findings.length > 0) evidenceSummaries.push(summary);
|
|
7620
|
+
}
|
|
7621
|
+
if (evidenceSummaries.length > 0) {
|
|
7622
|
+
console.log("Cross-repo implementation evidence:");
|
|
7623
|
+
for (const s of evidenceSummaries) {
|
|
7624
|
+
console.log(` ${s.id}:`);
|
|
7625
|
+
for (const f of s.findings) {
|
|
7626
|
+
const icon = f.severity === "blocking" ? "\u2717" : f.severity === "warning" ? "!" : "\xB7";
|
|
7627
|
+
console.log(` ${icon} ${f.message}`);
|
|
7628
|
+
}
|
|
7629
|
+
}
|
|
7630
|
+
console.log("");
|
|
7631
|
+
}
|
|
7632
|
+
}
|
|
7469
7633
|
const ownerMap = loadOwners(dir);
|
|
7470
7634
|
const matchedDomains = collectMatchedDomains(activeMatches.map((m) => m.artifact.domains));
|
|
7471
7635
|
const affectedOwners = resolveAffectedOwners(matchedDomains, ownerMap);
|
|
@@ -10089,6 +10253,7 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
10089
10253
|
const selectedWIs = workItems.filter((a) => a.lifecycle && includedSet.has(a.lifecycle));
|
|
10090
10254
|
for (const wi of selectedWIs) {
|
|
10091
10255
|
const id = wi.id || wi.title;
|
|
10256
|
+
if (!id || !id.trim()) continue;
|
|
10092
10257
|
const wiNodeId = `wi:${id}`;
|
|
10093
10258
|
addNode({
|
|
10094
10259
|
id: wiNodeId,
|
|
@@ -10099,16 +10264,19 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
10099
10264
|
knowledge_level: wi.knowledgeLevel || void 0
|
|
10100
10265
|
});
|
|
10101
10266
|
for (const glob of wi.codeGlobs) {
|
|
10267
|
+
if (!glob || !glob.trim()) continue;
|
|
10102
10268
|
const codeId = `code:${glob}`;
|
|
10103
10269
|
addNode({ id: codeId, type: "code-glob", label: glob });
|
|
10104
10270
|
addEdge(wiNodeId, codeId, "owns");
|
|
10105
10271
|
}
|
|
10106
10272
|
for (const cap of wi.capabilities) {
|
|
10273
|
+
if (!cap || !cap.trim()) continue;
|
|
10107
10274
|
const capId = `capability:${slug(cap) || cap}`;
|
|
10108
10275
|
addNode({ id: capId, type: "capability", label: cap });
|
|
10109
10276
|
addEdge(wiNodeId, capId, "implements");
|
|
10110
10277
|
}
|
|
10111
10278
|
for (const dec of wi.decisions) {
|
|
10279
|
+
if (!dec || !dec.trim()) continue;
|
|
10112
10280
|
const adrId = `adr:${dec}`;
|
|
10113
10281
|
addNode({ id: adrId, type: "decision", label: dec });
|
|
10114
10282
|
addEdge(wiNodeId, adrId, "depends_on");
|
|
@@ -10125,16 +10293,19 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
|
|
|
10125
10293
|
}
|
|
10126
10294
|
}
|
|
10127
10295
|
for (const adr of all.filter(isAdr)) {
|
|
10128
|
-
const
|
|
10296
|
+
const adrLabel = adr.id || adr.title;
|
|
10297
|
+
if (!adrLabel || !adrLabel.trim()) continue;
|
|
10298
|
+
const adrId = `adr:${adrLabel}`;
|
|
10129
10299
|
const referenced = nodes.has(adrId);
|
|
10130
10300
|
if (scope === "all" || referenced) {
|
|
10131
10301
|
nodes.set(adrId, {
|
|
10132
10302
|
id: adrId,
|
|
10133
10303
|
type: "decision",
|
|
10134
|
-
label: `${adr.id} ${adr.title}`.trim() ||
|
|
10304
|
+
label: `${adr.id} ${adr.title}`.trim() || adrLabel,
|
|
10135
10305
|
path: adr.relPath
|
|
10136
10306
|
});
|
|
10137
10307
|
for (const glob of adr.codeGlobs) {
|
|
10308
|
+
if (!glob || !glob.trim()) continue;
|
|
10138
10309
|
const codeId = `code:${glob}`;
|
|
10139
10310
|
addNode({ id: codeId, type: "code-glob", label: glob });
|
|
10140
10311
|
addEdge(adrId, codeId, "governs");
|
|
@@ -10190,13 +10361,20 @@ function renderGraphMermaid(graph) {
|
|
|
10190
10361
|
safeIds.set(id, candidate);
|
|
10191
10362
|
return candidate;
|
|
10192
10363
|
};
|
|
10193
|
-
const escapeLabel = (s) => s.replace(/"/g, "'");
|
|
10364
|
+
const escapeLabel = (s) => s.replace(/"/g, "'").replace(/\[/g, "(").replace(/\]/g, ")").replace(/\n/g, " ");
|
|
10365
|
+
const validNodes = graph.nodes.filter(
|
|
10366
|
+
(n) => n.id && n.id.trim() !== "" && n.label && n.label.trim() !== ""
|
|
10367
|
+
);
|
|
10368
|
+
const validNodeIds = new Set(validNodes.map((n) => n.id));
|
|
10194
10369
|
const lines = ["flowchart LR"];
|
|
10195
|
-
for (const node of
|
|
10370
|
+
for (const node of validNodes) {
|
|
10196
10371
|
lines.push(` ${safe(node.id)}["${escapeLabel(node.label)}"]`);
|
|
10197
10372
|
}
|
|
10198
|
-
|
|
10199
|
-
|
|
10373
|
+
const validEdges = graph.edges.filter(
|
|
10374
|
+
(e) => validNodeIds.has(e.from) && validNodeIds.has(e.to)
|
|
10375
|
+
);
|
|
10376
|
+
if (validEdges.length > 0) lines.push("");
|
|
10377
|
+
for (const edge of validEdges) {
|
|
10200
10378
|
lines.push(` ${safe(edge.from)} -->|${edge.type}| ${safe(edge.to)}`);
|
|
10201
10379
|
}
|
|
10202
10380
|
return lines.join("\n") + "\n";
|
|
@@ -11573,6 +11751,8 @@ function workItemsSignal(dir) {
|
|
|
11573
11751
|
if (wis.length === 0) return "none";
|
|
11574
11752
|
if (wis.some((w) => w.lifecycle === "in-progress")) return "in-progress";
|
|
11575
11753
|
if (wis.some((w) => w.lifecycle === "ready")) return "ready";
|
|
11754
|
+
const hasActive = wis.some((w) => w.lifecycle === "draft" || w.lifecycle === "blocked");
|
|
11755
|
+
if (!hasActive && wis.some((w) => w.lifecycle === "completed" || w.lifecycle === "archived")) return "completed-only";
|
|
11576
11756
|
return "none-ready";
|
|
11577
11757
|
}
|
|
11578
11758
|
function installedAdapters(dir) {
|
|
@@ -12001,6 +12181,8 @@ function buildReadinessReport(dir, now = /* @__PURE__ */ new Date()) {
|
|
|
12001
12181
|
else if (understand === "missing") overall = "scanned";
|
|
12002
12182
|
else if (firstWeak) overall = "knowledge-incomplete";
|
|
12003
12183
|
else if (oq.summary.blocking_open > 0) overall = "needs-decisions";
|
|
12184
|
+
else if (work_items === "in-progress") overall = "active-implementation";
|
|
12185
|
+
else if (work_items === "completed-only") overall = deriveDeliveryCompletedStatus(dir);
|
|
12004
12186
|
else if (roadmap !== "has-candidates") overall = "ready-for-roadmap";
|
|
12005
12187
|
else if (work_items === "none" || work_items === "none-ready") overall = "ready-for-work-item";
|
|
12006
12188
|
else overall = "ready-for-implementation";
|
|
@@ -12013,6 +12195,16 @@ function buildReadinessReport(dir, now = /* @__PURE__ */ new Date()) {
|
|
|
12013
12195
|
nextStepRecommendation: rec
|
|
12014
12196
|
};
|
|
12015
12197
|
}
|
|
12198
|
+
function deriveDeliveryCompletedStatus(dir) {
|
|
12199
|
+
const wis = discoverWorkItems(dir);
|
|
12200
|
+
const completed = wis.filter((w) => lifecycleStateOf({ status: w.status, filePath: w.filePath }) === "completed");
|
|
12201
|
+
if (completed.length === 0) return "delivery-completed";
|
|
12202
|
+
const hasReleaseBlocked = completed.some((w) => w.releaseStatus === "blocked");
|
|
12203
|
+
const hasReleaseReady = completed.some((w) => w.releaseStatus === "ready" || w.releaseStatus === "released");
|
|
12204
|
+
if (hasReleaseBlocked) return "delivery-completed-release-blocked";
|
|
12205
|
+
if (hasReleaseReady) return "delivery-completed-release-ready";
|
|
12206
|
+
return "delivery-completed";
|
|
12207
|
+
}
|
|
12016
12208
|
|
|
12017
12209
|
// src/core/assets.ts
|
|
12018
12210
|
import matter5 from "gray-matter";
|
|
@@ -12376,7 +12568,7 @@ var refineWorkItem = {
|
|
|
12376
12568
|
id: "refine-work-item",
|
|
12377
12569
|
label: "Refine Work Item",
|
|
12378
12570
|
evaluate: (ctx) => {
|
|
12379
|
-
if (ctx.readyWorkItems > 0 || ctx.inProgressWorkItems > 0) return { status: "done" };
|
|
12571
|
+
if (ctx.readyWorkItems > 0 || ctx.inProgressWorkItems > 0 || ctx.blockedWorkItems > 0 || ctx.completedWorkItems > 0 || ctx.archivedWorkItems > 0) return { status: "done" };
|
|
12380
12572
|
if (ctx.draftWorkItems > 0) {
|
|
12381
12573
|
return {
|
|
12382
12574
|
status: "current",
|
|
@@ -12627,7 +12819,9 @@ function buildRouteContext(dir) {
|
|
|
12627
12819
|
draftWorkItems: byState("draft"),
|
|
12628
12820
|
readyWorkItems: byState("ready"),
|
|
12629
12821
|
inProgressWorkItems: byState("in-progress"),
|
|
12822
|
+
blockedWorkItems: byState("blocked"),
|
|
12630
12823
|
completedWorkItems: byState("completed"),
|
|
12824
|
+
archivedWorkItems: byState("archived"),
|
|
12631
12825
|
ownershipCoverage: `${withOwnership}/${total}`,
|
|
12632
12826
|
ownershipComplete: total > 0 && withOwnership >= total,
|
|
12633
12827
|
decisionCandidates: td.candidates,
|
|
@@ -13041,7 +13235,46 @@ function buildProjectExplanation(dir) {
|
|
|
13041
13235
|
projectRoute: buildProjectRoute(dir),
|
|
13042
13236
|
scanSignals: loadScanSignals(dir),
|
|
13043
13237
|
metadataHealth: analyzeMetadataHealth(dir),
|
|
13044
|
-
isModuleRepo: config != null && isModule(config)
|
|
13238
|
+
isModuleRepo: config != null && isModule(config),
|
|
13239
|
+
deliverySummary: (() => {
|
|
13240
|
+
const completed = workItemArtifacts.filter((a) => lifecycleStateOf({ status: a.status, filePath: a.filePath }) === "completed");
|
|
13241
|
+
const archived = workItemArtifacts.filter((a) => lifecycleStateOf({ status: a.status, filePath: a.filePath }) === "archived");
|
|
13242
|
+
const active = workItemArtifacts.filter((a) => isActiveState(lifecycleStateOf({ status: a.status, filePath: a.filePath })));
|
|
13243
|
+
if (completed.length === 0 && archived.length === 0) return null;
|
|
13244
|
+
return {
|
|
13245
|
+
completedWorkItems: completed.length,
|
|
13246
|
+
archivedWorkItems: archived.length,
|
|
13247
|
+
activeWorkItems: active.length,
|
|
13248
|
+
implementationCompleted: completed.filter((a) => a.implementationStatus === "completed").length,
|
|
13249
|
+
releaseBlocked: completed.filter((a) => a.releaseStatus === "blocked").length,
|
|
13250
|
+
releaseReady: completed.filter((a) => a.releaseStatus === "ready" || a.releaseStatus === "released").length
|
|
13251
|
+
};
|
|
13252
|
+
})(),
|
|
13253
|
+
implementationEvidence: workItemArtifacts.filter((a) => a.affectedModules.length > 0).map((a) => {
|
|
13254
|
+
const fm2 = a.rawFrontmatter;
|
|
13255
|
+
const evidence = fm2.implementation_evidence;
|
|
13256
|
+
const repos = evidence?.repositories ?? {};
|
|
13257
|
+
const repoEvidence = {};
|
|
13258
|
+
for (const [repoId, data] of Object.entries(repos)) {
|
|
13259
|
+
repoEvidence[repoId] = {
|
|
13260
|
+
role: String(data.role ?? (repoId === "core" ? "core" : "module")),
|
|
13261
|
+
status: String(data.status ?? "unknown")
|
|
13262
|
+
};
|
|
13263
|
+
}
|
|
13264
|
+
const lc = lifecycleStateOf({ status: a.status, filePath: a.filePath });
|
|
13265
|
+
const historicalDefault = lc === "completed" || lc === "archived" ? "not-assessed" : "not-started";
|
|
13266
|
+
return {
|
|
13267
|
+
id: a.id || a.title,
|
|
13268
|
+
lifecycle: lc,
|
|
13269
|
+
implementationStatus: a.implementationStatus || historicalDefault,
|
|
13270
|
+
validationStatus: a.validationStatus || historicalDefault,
|
|
13271
|
+
releaseStatus: a.releaseStatus || "not-assessed",
|
|
13272
|
+
affectedModules: a.affectedModules,
|
|
13273
|
+
releaseGates: Array.isArray(fm2.release_gates) ? fm2.release_gates : [],
|
|
13274
|
+
completionExceptions: Array.isArray(fm2.completion_exceptions) ? fm2.completion_exceptions : [],
|
|
13275
|
+
repoEvidence
|
|
13276
|
+
};
|
|
13277
|
+
})
|
|
13045
13278
|
};
|
|
13046
13279
|
}
|
|
13047
13280
|
function stateLabel(state) {
|
|
@@ -13167,6 +13400,36 @@ function renderExplanationHuman(exp) {
|
|
|
13167
13400
|
}
|
|
13168
13401
|
lines.push("");
|
|
13169
13402
|
}
|
|
13403
|
+
if (exp.implementationEvidence.length > 0) {
|
|
13404
|
+
lines.push("## Implementation Evidence");
|
|
13405
|
+
for (const ev of exp.implementationEvidence) {
|
|
13406
|
+
lines.push("");
|
|
13407
|
+
lines.push(`### ${ev.id}`);
|
|
13408
|
+
lines.push(`- Lifecycle: ${ev.lifecycle.charAt(0).toUpperCase() + ev.lifecycle.slice(1)}`);
|
|
13409
|
+
lines.push(`- Implementation: ${ev.implementationStatus}`);
|
|
13410
|
+
lines.push(`- Validation: ${ev.validationStatus}`);
|
|
13411
|
+
lines.push(`- Release: ${ev.releaseStatus}`);
|
|
13412
|
+
if (Object.keys(ev.repoEvidence).length > 0) {
|
|
13413
|
+
lines.push("Repositories:");
|
|
13414
|
+
for (const [repoId, info] of Object.entries(ev.repoEvidence)) {
|
|
13415
|
+
lines.push(`- ${repoId} \u2014 ${info.status}`);
|
|
13416
|
+
}
|
|
13417
|
+
} else if (ev.affectedModules.length > 0) {
|
|
13418
|
+
lines.push(`Affected modules: ${ev.affectedModules.join(", ")}`);
|
|
13419
|
+
}
|
|
13420
|
+
const pendingGates = ev.releaseGates.filter((g) => g.status === "blocked" || g.status === "pending");
|
|
13421
|
+
if (pendingGates.length > 0) {
|
|
13422
|
+
lines.push("Pending release gates:");
|
|
13423
|
+
for (const g of pendingGates) lines.push(`- ${g.id}${g.reason ? ` \u2014 ${g.reason}` : ""}`);
|
|
13424
|
+
}
|
|
13425
|
+
const acceptedExceptions = ev.completionExceptions.filter((e) => e.status === "accepted" || e.status === "deferred");
|
|
13426
|
+
if (acceptedExceptions.length > 0) {
|
|
13427
|
+
lines.push("Completion exceptions:");
|
|
13428
|
+
for (const e of acceptedExceptions) lines.push(`- ${e.id}: ${e.status}${e.reason ? ` \u2014 ${e.reason}` : ""}`);
|
|
13429
|
+
}
|
|
13430
|
+
}
|
|
13431
|
+
lines.push("");
|
|
13432
|
+
}
|
|
13170
13433
|
if (exp.domains.length > 0) {
|
|
13171
13434
|
lines.push("## Domains");
|
|
13172
13435
|
lines.push(`- ${exp.domains.join(", ")}`);
|
|
@@ -13249,6 +13512,17 @@ function renderExplanationHuman(exp) {
|
|
|
13249
13512
|
}
|
|
13250
13513
|
lines.push(`- Next step: ${exp.readiness.recommended_next_step.label}`);
|
|
13251
13514
|
lines.push("");
|
|
13515
|
+
if (exp.deliverySummary) {
|
|
13516
|
+
const ds = exp.deliverySummary;
|
|
13517
|
+
lines.push("## Delivery Summary");
|
|
13518
|
+
lines.push(`- Completed Work Items: ${ds.completedWorkItems}`);
|
|
13519
|
+
if (ds.archivedWorkItems > 0) lines.push(`- Archived Work Items: ${ds.archivedWorkItems}`);
|
|
13520
|
+
lines.push(`- Active Work Items: ${ds.activeWorkItems}`);
|
|
13521
|
+
if (ds.implementationCompleted > 0) lines.push(`- Implementation completed: ${ds.implementationCompleted}`);
|
|
13522
|
+
if (ds.releaseBlocked > 0) lines.push(`- Release blocked: ${ds.releaseBlocked}`);
|
|
13523
|
+
if (ds.releaseReady > 0) lines.push(`- Release ready: ${ds.releaseReady}`);
|
|
13524
|
+
lines.push("");
|
|
13525
|
+
}
|
|
13252
13526
|
const rec = exp.nextStepRecommendation;
|
|
13253
13527
|
const secondary = rec.secondary ?? [];
|
|
13254
13528
|
const steps = secondary.length > 0 || /Delivery|Active|Maintenance/.test(rec.phase) ? [rec.label, ...secondary.map((s2) => s2.label)] : exp.suggestedNextSteps;
|
|
@@ -13283,6 +13557,10 @@ function renderExplanationHuman(exp) {
|
|
|
13283
13557
|
if (!exp.isModuleRepo) {
|
|
13284
13558
|
lines.push(`- roadmap: ${s.roadmap}`);
|
|
13285
13559
|
lines.push(`- work-items: ${s.work_items}`);
|
|
13560
|
+
if (exp.deliverySummary) {
|
|
13561
|
+
lines.push(`- work-items completed: ${exp.deliverySummary.completedWorkItems}`);
|
|
13562
|
+
lines.push(`- work-items active: ${exp.deliverySummary.activeWorkItems}`);
|
|
13563
|
+
}
|
|
13286
13564
|
lines.push(`- adapters: ${s.adapters.length > 0 ? s.adapters.join(", ") + " installed" : "none installed"}`);
|
|
13287
13565
|
} else {
|
|
13288
13566
|
lines.push("- roadmap: managed-by-core");
|
|
@@ -13624,16 +13902,23 @@ var OPERATING_RULES = [
|
|
|
13624
13902
|
"Kaddo itself never calls an LLM and never runs git \u2014 every git action is the human\u2019s."
|
|
13625
13903
|
];
|
|
13626
13904
|
function toContextWorkItem(a) {
|
|
13627
|
-
|
|
13905
|
+
const lifecycle = lifecycleStateOf({ status: a.status, filePath: a.filePath });
|
|
13906
|
+
const isHistorical = lifecycle === "completed" || lifecycle === "archived";
|
|
13907
|
+
const wi = {
|
|
13628
13908
|
id: a.id,
|
|
13629
13909
|
type: a.type,
|
|
13630
13910
|
title: a.title,
|
|
13631
13911
|
status: a.status,
|
|
13632
|
-
lifecycle
|
|
13912
|
+
lifecycle,
|
|
13633
13913
|
knowledgeLevel: a.knowledgeLevel,
|
|
13634
13914
|
domains: a.domains,
|
|
13635
13915
|
source: parseWorkItemSource(a.rawFrontmatter)
|
|
13636
13916
|
};
|
|
13917
|
+
wi.implementationStatus = a.implementationStatus || (isHistorical ? "not-assessed" : void 0);
|
|
13918
|
+
wi.validationStatus = a.validationStatus || (isHistorical ? "not-assessed" : void 0);
|
|
13919
|
+
wi.releaseStatus = a.releaseStatus || (isHistorical ? "not-assessed" : void 0);
|
|
13920
|
+
if (a.affectedModules.length > 0) wi.affectedModules = a.affectedModules;
|
|
13921
|
+
return wi;
|
|
13637
13922
|
}
|
|
13638
13923
|
function toContextArtifact(a) {
|
|
13639
13924
|
return { id: a.id, type: a.type, title: a.title, summary: a.summary, codeGlobs: a.codeGlobs };
|
|
@@ -13662,10 +13947,17 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
|
|
|
13662
13947
|
missing.push("No roadmap baseline found.");
|
|
13663
13948
|
}
|
|
13664
13949
|
const allArtifacts = discoverKnowledge(dir);
|
|
13665
|
-
const
|
|
13666
|
-
|
|
13950
|
+
const allWorkItemArtifacts = allArtifacts.filter((a) => isDeliveryWorkItem(a));
|
|
13951
|
+
const workItems = allWorkItemArtifacts.filter(
|
|
13952
|
+
(a) => isActiveState(lifecycleStateOf({ status: a.status, filePath: a.filePath }))
|
|
13953
|
+
);
|
|
13954
|
+
const completedWorkItems = allWorkItemArtifacts.filter(
|
|
13955
|
+
(a) => lifecycleStateOf({ status: a.status, filePath: a.filePath }) === "completed"
|
|
13956
|
+
);
|
|
13957
|
+
const archivedWorkItems = allWorkItemArtifacts.filter(
|
|
13958
|
+
(a) => lifecycleStateOf({ status: a.status, filePath: a.filePath }) === "archived"
|
|
13667
13959
|
);
|
|
13668
|
-
if (
|
|
13960
|
+
if (allWorkItemArtifacts.length === 0 && !moduleRepo) {
|
|
13669
13961
|
missing.push("No work items found.");
|
|
13670
13962
|
}
|
|
13671
13963
|
const state = config.project.state;
|
|
@@ -13780,6 +14072,10 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
|
|
|
13780
14072
|
roadmapSummary,
|
|
13781
14073
|
inventoryAvailable,
|
|
13782
14074
|
workItems: workItems.map(toContextWorkItem),
|
|
14075
|
+
activeWorkItems: workItems.map(toContextWorkItem),
|
|
14076
|
+
completedWorkItems: completedWorkItems.map(toContextWorkItem),
|
|
14077
|
+
archivedWorkItems: archivedWorkItems.map(toContextWorkItem),
|
|
14078
|
+
allWorkItems: allWorkItemArtifacts.map(toContextWorkItem),
|
|
13783
14079
|
artifacts: allArtifacts.filter((a) => a.codeGlobs.length > 0).map(toContextArtifact)
|
|
13784
14080
|
},
|
|
13785
14081
|
layers,
|
|
@@ -13796,6 +14092,30 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
|
|
|
13796
14092
|
const compact = (a) => ({ total: a.total, installed: a.total - a.missing, outdated: a.outdated, unknown_version: a.unknown_version, modified: a.modified });
|
|
13797
14093
|
return { version: s.version, agents: compact(s.agents), skills: compact(s.skills) };
|
|
13798
14094
|
})(),
|
|
14095
|
+
workItemSummary: (() => {
|
|
14096
|
+
const lc = (s) => allWorkItemArtifacts.filter(
|
|
14097
|
+
(a) => lifecycleStateOf({ status: a.status, filePath: a.filePath }) === s
|
|
14098
|
+
).length;
|
|
14099
|
+
const draft = lc("draft");
|
|
14100
|
+
const ready = lc("ready");
|
|
14101
|
+
const inProgress = lc("in-progress");
|
|
14102
|
+
const blocked = lc("blocked");
|
|
14103
|
+
const completed = lc("completed");
|
|
14104
|
+
const archived = lc("archived");
|
|
14105
|
+
const active = draft + ready + inProgress + blocked;
|
|
14106
|
+
const total = allWorkItemArtifacts.length;
|
|
14107
|
+
let summary = "none";
|
|
14108
|
+
if (total === 0) summary = "none";
|
|
14109
|
+
else if (active === 0 && completed > 0 && archived === 0) summary = "completed-only";
|
|
14110
|
+
else if (active === 0 && archived > 0 && completed === 0) summary = "archived-only";
|
|
14111
|
+
else if (active === 0 && completed + archived > 0) summary = "historical-only";
|
|
14112
|
+
else if (inProgress > 0) summary = "active";
|
|
14113
|
+
else if (blocked > 0 && ready === 0 && inProgress === 0) summary = "blocked";
|
|
14114
|
+
else if (ready > 0) summary = "ready";
|
|
14115
|
+
else if (draft > 0 && ready === 0) summary = "draft-only";
|
|
14116
|
+
else summary = "mixed-active";
|
|
14117
|
+
return { total, active, draft, ready, in_progress: inProgress, blocked, completed, archived, summary };
|
|
14118
|
+
})(),
|
|
13799
14119
|
deliveryMix,
|
|
13800
14120
|
external: loadExternalCapsules(dir),
|
|
13801
14121
|
graph: loadGraphSummary(dir),
|
|
@@ -14724,6 +15044,16 @@ function runUnderstand() {
|
|
|
14724
15044
|
} else if (rec.agentPath) {
|
|
14725
15045
|
console.log(` Agent prompt: ${rec.agentPath}`);
|
|
14726
15046
|
}
|
|
15047
|
+
if (exp.deliverySummary) {
|
|
15048
|
+
const ds2 = exp.deliverySummary;
|
|
15049
|
+
console.log("");
|
|
15050
|
+
console.log("Previous delivery:");
|
|
15051
|
+
console.log(` - Completed Work Items: ${ds2.completedWorkItems}`);
|
|
15052
|
+
if (ds2.implementationCompleted > 0) console.log(` - Implementation completed: ${ds2.implementationCompleted}`);
|
|
15053
|
+
if (ds2.releaseBlocked > 0) console.log(` - Release blocked: ${ds2.releaseBlocked}`);
|
|
15054
|
+
if (ds2.releaseReady > 0) console.log(` - Release ready: ${ds2.releaseReady}`);
|
|
15055
|
+
console.log(` - Current active work: ${ds2.activeWorkItems > 0 ? ds2.activeWorkItems : "none"}`);
|
|
15056
|
+
}
|
|
14727
15057
|
if (rec.skill) console.log(`Recommended skill: ${rec.skill}`);
|
|
14728
15058
|
console.log(`Next step: ${rec.label}`);
|
|
14729
15059
|
if (rec.reason) console.log(`Why: ${rec.reason}`);
|
|
@@ -15142,7 +15472,7 @@ function findWorkItemFile(dir, id) {
|
|
|
15142
15472
|
function updateWorkItemFile(filePath, learning) {
|
|
15143
15473
|
const raw = readFile(filePath);
|
|
15144
15474
|
const { data, content } = matter8(raw);
|
|
15145
|
-
data.status = "
|
|
15475
|
+
data.status = "completed";
|
|
15146
15476
|
data.completed_at = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
15147
15477
|
let updatedContent = content;
|
|
15148
15478
|
if (content.includes("_What did we learn from this change? Update after completion._")) {
|
|
@@ -15177,24 +15507,24 @@ async function runLearn(artifactId) {
|
|
|
15177
15507
|
}
|
|
15178
15508
|
intro2("kaddo learn");
|
|
15179
15509
|
const artifacts = readArtifacts(join(dir, ARCH_DIR9));
|
|
15180
|
-
const
|
|
15181
|
-
(a) => a.status === "in-progress" && a.type !== "current-state" && a.type !== "roadmap"
|
|
15510
|
+
const closable = artifacts.filter(
|
|
15511
|
+
(a) => (a.status === "in-progress" || a.status === "completed" || a.status === "done") && a.type !== "current-state" && a.type !== "roadmap"
|
|
15182
15512
|
);
|
|
15183
|
-
if (
|
|
15184
|
-
log2.warn("No in-progress work items found.");
|
|
15513
|
+
if (closable.length === 0) {
|
|
15514
|
+
log2.warn("No in-progress or completed work items found.");
|
|
15185
15515
|
outro2("Nothing to close.");
|
|
15186
15516
|
return;
|
|
15187
15517
|
}
|
|
15188
15518
|
let targetId;
|
|
15189
15519
|
if (artifactId) {
|
|
15190
15520
|
targetId = artifactId;
|
|
15191
|
-
} else if (
|
|
15192
|
-
targetId =
|
|
15193
|
-
log2.info(`Closing: ${targetId} \u2014 ${
|
|
15521
|
+
} else if (closable.length === 1) {
|
|
15522
|
+
targetId = closable[0].id || closable[0].title;
|
|
15523
|
+
log2.info(`Closing: ${targetId} \u2014 ${closable[0].summary || closable[0].title}`);
|
|
15194
15524
|
} else {
|
|
15195
15525
|
const chosen = await select2({
|
|
15196
15526
|
message: "Which work item are you closing?",
|
|
15197
|
-
options:
|
|
15527
|
+
options: closable.map((a) => ({
|
|
15198
15528
|
value: a.id || a.title,
|
|
15199
15529
|
label: `${a.id || a.title} \u2014 ${a.summary || a.title}`
|
|
15200
15530
|
}))
|
|
@@ -15211,9 +15541,26 @@ async function runLearn(artifactId) {
|
|
|
15211
15541
|
placeholder: "e.g. The retry logic needed a separate queue to avoid blocking the main flow",
|
|
15212
15542
|
validate: (v) => v.trim().length === 0 ? "Learning is required." : void 0
|
|
15213
15543
|
});
|
|
15214
|
-
|
|
15215
|
-
|
|
15544
|
+
const wiRaw = readFile(filePath);
|
|
15545
|
+
const wiData = matter8(wiRaw).data;
|
|
15546
|
+
const hasExceptions = wiData.validation_status === "accepted-with-exceptions" || Array.isArray(wiData.completion_exceptions) && wiData.completion_exceptions.length > 0;
|
|
15547
|
+
const releaseBlocked = wiData.release_status === "blocked";
|
|
15548
|
+
let enrichedLearning = learning.trim();
|
|
15549
|
+
if (hasExceptions || releaseBlocked) {
|
|
15550
|
+
const notes = [];
|
|
15551
|
+
if (hasExceptions) notes.push("Validation exceptions were accepted for this Work Item.");
|
|
15552
|
+
if (releaseBlocked) {
|
|
15553
|
+
const gates = Array.isArray(wiData.release_gates) ? wiData.release_gates.filter((g) => g.status === "blocked" || g.status === "pending").map((g) => g.id) : [];
|
|
15554
|
+
notes.push(gates.length > 0 ? `Release gates remain: ${gates.join(", ")}.` : "Production release remains blocked.");
|
|
15555
|
+
}
|
|
15556
|
+
enrichedLearning += "\n\n> " + notes.join(" ");
|
|
15557
|
+
}
|
|
15558
|
+
updateWorkItemFile(filePath, enrichedLearning);
|
|
15559
|
+
log2.success(`${targetId} marked as completed`);
|
|
15216
15560
|
log2.success(`Learning recorded in ${filePath.replace(dir + "/", "")}`);
|
|
15561
|
+
if (hasExceptions) {
|
|
15562
|
+
log2.warn("Learning captured from a Work Item completed with validation exceptions.");
|
|
15563
|
+
}
|
|
15217
15564
|
log2.info("Consider updating knowledge/knowledge.md if this changes the current state.");
|
|
15218
15565
|
outro2("Work item closed.");
|
|
15219
15566
|
}
|