@kaddo/cli 3.65.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 +263 -20
- 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);
|
|
@@ -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";
|
|
@@ -13041,7 +13209,30 @@ function buildProjectExplanation(dir) {
|
|
|
13041
13209
|
projectRoute: buildProjectRoute(dir),
|
|
13042
13210
|
scanSignals: loadScanSignals(dir),
|
|
13043
13211
|
metadataHealth: analyzeMetadataHealth(dir),
|
|
13044
|
-
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
|
+
})
|
|
13045
13236
|
};
|
|
13046
13237
|
}
|
|
13047
13238
|
function stateLabel(state) {
|
|
@@ -13167,6 +13358,36 @@ function renderExplanationHuman(exp) {
|
|
|
13167
13358
|
}
|
|
13168
13359
|
lines.push("");
|
|
13169
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
|
+
}
|
|
13170
13391
|
if (exp.domains.length > 0) {
|
|
13171
13392
|
lines.push("## Domains");
|
|
13172
13393
|
lines.push(`- ${exp.domains.join(", ")}`);
|
|
@@ -13624,7 +13845,7 @@ var OPERATING_RULES = [
|
|
|
13624
13845
|
"Kaddo itself never calls an LLM and never runs git \u2014 every git action is the human\u2019s."
|
|
13625
13846
|
];
|
|
13626
13847
|
function toContextWorkItem(a) {
|
|
13627
|
-
|
|
13848
|
+
const wi = {
|
|
13628
13849
|
id: a.id,
|
|
13629
13850
|
type: a.type,
|
|
13630
13851
|
title: a.title,
|
|
@@ -13634,6 +13855,11 @@ function toContextWorkItem(a) {
|
|
|
13634
13855
|
domains: a.domains,
|
|
13635
13856
|
source: parseWorkItemSource(a.rawFrontmatter)
|
|
13636
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;
|
|
13637
13863
|
}
|
|
13638
13864
|
function toContextArtifact(a) {
|
|
13639
13865
|
return { id: a.id, type: a.type, title: a.title, summary: a.summary, codeGlobs: a.codeGlobs };
|
|
@@ -15142,7 +15368,7 @@ function findWorkItemFile(dir, id) {
|
|
|
15142
15368
|
function updateWorkItemFile(filePath, learning) {
|
|
15143
15369
|
const raw = readFile(filePath);
|
|
15144
15370
|
const { data, content } = matter8(raw);
|
|
15145
|
-
data.status = "
|
|
15371
|
+
data.status = "completed";
|
|
15146
15372
|
data.completed_at = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
15147
15373
|
let updatedContent = content;
|
|
15148
15374
|
if (content.includes("_What did we learn from this change? Update after completion._")) {
|
|
@@ -15177,24 +15403,24 @@ async function runLearn(artifactId) {
|
|
|
15177
15403
|
}
|
|
15178
15404
|
intro2("kaddo learn");
|
|
15179
15405
|
const artifacts = readArtifacts(join(dir, ARCH_DIR9));
|
|
15180
|
-
const
|
|
15181
|
-
(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"
|
|
15182
15408
|
);
|
|
15183
|
-
if (
|
|
15184
|
-
log2.warn("No in-progress work items found.");
|
|
15409
|
+
if (closable.length === 0) {
|
|
15410
|
+
log2.warn("No in-progress or completed work items found.");
|
|
15185
15411
|
outro2("Nothing to close.");
|
|
15186
15412
|
return;
|
|
15187
15413
|
}
|
|
15188
15414
|
let targetId;
|
|
15189
15415
|
if (artifactId) {
|
|
15190
15416
|
targetId = artifactId;
|
|
15191
|
-
} else if (
|
|
15192
|
-
targetId =
|
|
15193
|
-
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}`);
|
|
15194
15420
|
} else {
|
|
15195
15421
|
const chosen = await select2({
|
|
15196
15422
|
message: "Which work item are you closing?",
|
|
15197
|
-
options:
|
|
15423
|
+
options: closable.map((a) => ({
|
|
15198
15424
|
value: a.id || a.title,
|
|
15199
15425
|
label: `${a.id || a.title} \u2014 ${a.summary || a.title}`
|
|
15200
15426
|
}))
|
|
@@ -15211,9 +15437,26 @@ async function runLearn(artifactId) {
|
|
|
15211
15437
|
placeholder: "e.g. The retry logic needed a separate queue to avoid blocking the main flow",
|
|
15212
15438
|
validate: (v) => v.trim().length === 0 ? "Learning is required." : void 0
|
|
15213
15439
|
});
|
|
15214
|
-
|
|
15215
|
-
|
|
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`);
|
|
15216
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
|
+
}
|
|
15217
15460
|
log2.info("Consider updating knowledge/knowledge.md if this changes the current state.");
|
|
15218
15461
|
outro2("Work item closed.");
|
|
15219
15462
|
}
|