@kaddo/cli 3.72.2 → 3.74.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/admin-dist/assets/index-AAaxSvT4.js +38 -0
- package/dist/admin-dist/assets/index-BPt-9--k.css +2 -0
- package/dist/admin-dist/index.html +2 -2
- package/dist/admin-server/index.js +740 -225
- package/dist/core.js +757 -11
- package/package.json +1 -1
- package/dist/admin-dist/assets/index-D4sr_XCt.css +0 -2
- package/dist/admin-dist/assets/index-DEZExhWd.js +0 -38
package/dist/core.js
CHANGED
|
@@ -116,8 +116,8 @@ function loadConfig(dir) {
|
|
|
116
116
|
const parsed = configSchema.safeParse(raw ?? {});
|
|
117
117
|
if (!parsed.success) {
|
|
118
118
|
const issues = parsed.error.issues.map((i) => {
|
|
119
|
-
const
|
|
120
|
-
return
|
|
119
|
+
const path3 = i.path.join(".");
|
|
120
|
+
return path3 ? ` - ${path3}: ${i.message}` : ` - ${i.message}`;
|
|
121
121
|
}).join("\n");
|
|
122
122
|
throw new ConfigError(`Invalid .kaddo/config.yml:
|
|
123
123
|
${issues}`);
|
|
@@ -352,11 +352,11 @@ function moduleArtifactCoverage(dir, id) {
|
|
|
352
352
|
};
|
|
353
353
|
}
|
|
354
354
|
function loadMappedModules(dir) {
|
|
355
|
-
const
|
|
356
|
-
if (!exists(
|
|
355
|
+
const path3 = join(dir, DESCRIPTOR_PATH);
|
|
356
|
+
if (!exists(path3)) return [];
|
|
357
357
|
let parsed;
|
|
358
358
|
try {
|
|
359
|
-
parsed = parseYaml3(readFile(
|
|
359
|
+
parsed = parseYaml3(readFile(path3));
|
|
360
360
|
} catch {
|
|
361
361
|
return [];
|
|
362
362
|
}
|
|
@@ -1590,7 +1590,7 @@ function sectionParagraph(md, title) {
|
|
|
1590
1590
|
}
|
|
1591
1591
|
return "";
|
|
1592
1592
|
}
|
|
1593
|
-
function parseCapsule(id,
|
|
1593
|
+
function parseCapsule(id, path3, md) {
|
|
1594
1594
|
const { data } = matter2(md);
|
|
1595
1595
|
const updatedAt = data.updated_at ? String(data.updated_at) : void 0;
|
|
1596
1596
|
let ageDays = null;
|
|
@@ -1600,7 +1600,7 @@ function parseCapsule(id, path2, md) {
|
|
|
1600
1600
|
}
|
|
1601
1601
|
return {
|
|
1602
1602
|
id,
|
|
1603
|
-
path:
|
|
1603
|
+
path: path3,
|
|
1604
1604
|
system: data.system ? String(data.system) : id,
|
|
1605
1605
|
owner: data.owner ? String(data.owner) : void 0,
|
|
1606
1606
|
updatedAt,
|
|
@@ -2761,10 +2761,10 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
|
|
|
2761
2761
|
const q = (rel) => analyzeKnowledgeArtifact(dir, rel);
|
|
2762
2762
|
const resolveAgent = (agent) => {
|
|
2763
2763
|
const file = agent.endsWith(".md") ? agent : `${agent}.md`;
|
|
2764
|
-
const
|
|
2765
|
-
const installed = isFile(join(dir,
|
|
2764
|
+
const path3 = agentInstallPath(file);
|
|
2765
|
+
const installed = isFile(join(dir, path3));
|
|
2766
2766
|
const group = agentGroupOf(file);
|
|
2767
|
-
return { agentPath:
|
|
2767
|
+
return { agentPath: path3, agentInstalled: installed, installCommand: installed ? void 0 : `kaddo add agents --group ${group}` };
|
|
2768
2768
|
};
|
|
2769
2769
|
const moduleRepo = isModule(config);
|
|
2770
2770
|
const coreRepo = isCore(config);
|
|
@@ -3686,6 +3686,9 @@ var RECOMMENDED_SKILLS = [...SKILL_GROUPS.delivery, ...SKILL_GROUPS.tech];
|
|
|
3686
3686
|
function skillInstallPath(id) {
|
|
3687
3687
|
return `knowledge/skills/${id}/skill.md`;
|
|
3688
3688
|
}
|
|
3689
|
+
function skillById(id) {
|
|
3690
|
+
return SKILLS.find((s) => s.id === id);
|
|
3691
|
+
}
|
|
3689
3692
|
|
|
3690
3693
|
// src/agents/responsibility.ts
|
|
3691
3694
|
var RESPONSIBILITY_MATRIX = {
|
|
@@ -7715,18 +7718,757 @@ function parseRelatedKnowledge(fm, knowledgeById) {
|
|
|
7715
7718
|
}
|
|
7716
7719
|
return out;
|
|
7717
7720
|
}
|
|
7721
|
+
|
|
7722
|
+
// src/core/work-item-write.ts
|
|
7723
|
+
import fs2 from "fs";
|
|
7724
|
+
import path2 from "path";
|
|
7725
|
+
import crypto from "crypto";
|
|
7726
|
+
import matter7 from "gray-matter";
|
|
7727
|
+
var WORK_ITEMS_DIR = "knowledge/delivery/work-items";
|
|
7728
|
+
var WorkItemWriteError = class extends Error {
|
|
7729
|
+
constructor(code, message) {
|
|
7730
|
+
super(message);
|
|
7731
|
+
this.code = code;
|
|
7732
|
+
this.name = "WorkItemWriteError";
|
|
7733
|
+
}
|
|
7734
|
+
code;
|
|
7735
|
+
};
|
|
7736
|
+
var VALID_COVERAGE = /* @__PURE__ */ new Set(["affected", "reviewed-not-affected", "unknown", "not-applicable"]);
|
|
7737
|
+
var VALID_CONFIDENCE = /* @__PURE__ */ new Set(["high", "medium", "low"]);
|
|
7738
|
+
var EDITABLE_STATES = ["draft"];
|
|
7739
|
+
function revisionOf(raw) {
|
|
7740
|
+
return crypto.createHash("sha256").update(raw, "utf-8").digest("hex");
|
|
7741
|
+
}
|
|
7742
|
+
function slugify2(s) {
|
|
7743
|
+
return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 50);
|
|
7744
|
+
}
|
|
7745
|
+
function nextWorkItemId(dir) {
|
|
7746
|
+
const wiDir = join(dir, WORK_ITEMS_DIR);
|
|
7747
|
+
let max = 0;
|
|
7748
|
+
const walk = (d) => {
|
|
7749
|
+
if (!exists(d)) return;
|
|
7750
|
+
for (const entry of fs2.readdirSync(d)) {
|
|
7751
|
+
const full = join(d, entry);
|
|
7752
|
+
if (isFile(full)) {
|
|
7753
|
+
const m = entry.match(/WI-(\d+)/);
|
|
7754
|
+
if (m) max = Math.max(max, parseInt(m[1], 10));
|
|
7755
|
+
} else if (!entry.startsWith(".")) {
|
|
7756
|
+
walk(full);
|
|
7757
|
+
}
|
|
7758
|
+
}
|
|
7759
|
+
};
|
|
7760
|
+
walk(wiDir);
|
|
7761
|
+
return `WI-${String(max + 1).padStart(3, "0")}`;
|
|
7762
|
+
}
|
|
7763
|
+
function atomicWrite(filePath, content) {
|
|
7764
|
+
fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
|
|
7765
|
+
const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
7766
|
+
fs2.writeFileSync(tmp, content, "utf-8");
|
|
7767
|
+
try {
|
|
7768
|
+
fs2.renameSync(tmp, filePath);
|
|
7769
|
+
} catch (err) {
|
|
7770
|
+
try {
|
|
7771
|
+
fs2.rmSync(tmp, { force: true });
|
|
7772
|
+
} catch {
|
|
7773
|
+
}
|
|
7774
|
+
throw err;
|
|
7775
|
+
}
|
|
7776
|
+
}
|
|
7777
|
+
function findArtifact(dir, id) {
|
|
7778
|
+
const match = discoverWorkItems(dir).find((a) => (a.id || a.title) === id);
|
|
7779
|
+
if (!match) throw new WorkItemWriteError("WORK_ITEM_NOT_FOUND", `Work Item "${id}" was not found.`);
|
|
7780
|
+
return { filePath: match.filePath, relPath: match.relPath };
|
|
7781
|
+
}
|
|
7782
|
+
function validModuleIds(dir) {
|
|
7783
|
+
const ids = /* @__PURE__ */ new Set(["core"]);
|
|
7784
|
+
for (const m of loadMappedModules(dir)) ids.add(m.id);
|
|
7785
|
+
return ids;
|
|
7786
|
+
}
|
|
7787
|
+
var SECTION_ORDER = [
|
|
7788
|
+
"Actor",
|
|
7789
|
+
"Outcome",
|
|
7790
|
+
"Current behavior",
|
|
7791
|
+
"Target behavior",
|
|
7792
|
+
"Entry points",
|
|
7793
|
+
"End-to-end flow",
|
|
7794
|
+
"Scope unknowns",
|
|
7795
|
+
"Acceptance criteria"
|
|
7796
|
+
];
|
|
7797
|
+
var KNOWN_HEADINGS = new Set(SECTION_ORDER.map((s) => s.toLowerCase()));
|
|
7798
|
+
function parseBody(content) {
|
|
7799
|
+
const lines = content.split(/\r?\n/);
|
|
7800
|
+
const preamble = [];
|
|
7801
|
+
const sections = [];
|
|
7802
|
+
let current = null;
|
|
7803
|
+
let buffer = [];
|
|
7804
|
+
let seenHeading = false;
|
|
7805
|
+
const flush = () => {
|
|
7806
|
+
if (current) {
|
|
7807
|
+
current.body = buffer.join("\n").trim();
|
|
7808
|
+
sections.push(current);
|
|
7809
|
+
}
|
|
7810
|
+
buffer = [];
|
|
7811
|
+
};
|
|
7812
|
+
for (const line of lines) {
|
|
7813
|
+
const m = line.match(/^##\s+(.*?)\s*$/);
|
|
7814
|
+
if (m) {
|
|
7815
|
+
seenHeading = true;
|
|
7816
|
+
flush();
|
|
7817
|
+
current = { heading: m[1].trim(), normalized: m[1].trim().toLowerCase(), body: "" };
|
|
7818
|
+
} else if (!seenHeading) {
|
|
7819
|
+
preamble.push(line);
|
|
7820
|
+
} else {
|
|
7821
|
+
buffer.push(line);
|
|
7822
|
+
}
|
|
7823
|
+
}
|
|
7824
|
+
flush();
|
|
7825
|
+
return { preamble, sections };
|
|
7826
|
+
}
|
|
7827
|
+
function renderList(items) {
|
|
7828
|
+
return items.map((i) => `- ${i.trim()}`).filter((l) => l.trim() !== "-").join("\n");
|
|
7829
|
+
}
|
|
7830
|
+
function renderCriteria(items) {
|
|
7831
|
+
return items.filter((c) => c.text.trim()).map((c) => c.checked === true ? `- [x] ${c.text.trim()}` : c.checked === false ? `- [ ] ${c.text.trim()}` : `- ${c.text.trim()}`).join("\n");
|
|
7832
|
+
}
|
|
7833
|
+
function desiredSections(input) {
|
|
7834
|
+
const d = /* @__PURE__ */ new Map();
|
|
7835
|
+
const put = (heading, value) => d.set(heading.toLowerCase(), value.trim() ? value.trim() : null);
|
|
7836
|
+
put("Actor", input.actor ?? "");
|
|
7837
|
+
put("Outcome", input.outcome ?? "");
|
|
7838
|
+
put("Current behavior", input.currentBehavior ?? "");
|
|
7839
|
+
put("Target behavior", input.targetBehavior ?? "");
|
|
7840
|
+
put("Entry points", input.entryPoints ?? "");
|
|
7841
|
+
put("End-to-end flow", input.endToEndFlow ?? "");
|
|
7842
|
+
d.set("scope unknowns", input.scopeUnknowns.some((u) => u.trim()) ? renderList(input.scopeUnknowns) : null);
|
|
7843
|
+
d.set("acceptance criteria", input.acceptanceCriteria.some((c) => c.text.trim()) ? renderCriteria(input.acceptanceCriteria) : null);
|
|
7844
|
+
return d;
|
|
7845
|
+
}
|
|
7846
|
+
function headingFor(normalized) {
|
|
7847
|
+
return SECTION_ORDER.find((s) => s.toLowerCase() === normalized) ?? normalized;
|
|
7848
|
+
}
|
|
7849
|
+
function mergeBody(existing, input) {
|
|
7850
|
+
const { preamble, sections } = parseBody(existing);
|
|
7851
|
+
const desired = desiredSections(input);
|
|
7852
|
+
const handled = /* @__PURE__ */ new Set();
|
|
7853
|
+
const preambleLines = [...preamble];
|
|
7854
|
+
const h1Index = preambleLines.findIndex((l) => /^#\s+/.test(l));
|
|
7855
|
+
if (h1Index >= 0) preambleLines[h1Index] = `# ${input.title}`;
|
|
7856
|
+
const out = [];
|
|
7857
|
+
for (const s of sections) {
|
|
7858
|
+
if (KNOWN_HEADINGS.has(s.normalized)) {
|
|
7859
|
+
handled.add(s.normalized);
|
|
7860
|
+
const body = desired.get(s.normalized);
|
|
7861
|
+
if (body != null) out.push(`## ${headingFor(s.normalized)}
|
|
7862
|
+
|
|
7863
|
+
${body}`);
|
|
7864
|
+
} else {
|
|
7865
|
+
out.push(`## ${s.heading}${s.body ? `
|
|
7866
|
+
|
|
7867
|
+
${s.body}` : ""}`);
|
|
7868
|
+
}
|
|
7869
|
+
}
|
|
7870
|
+
for (const heading of SECTION_ORDER) {
|
|
7871
|
+
const norm = heading.toLowerCase();
|
|
7872
|
+
if (handled.has(norm)) continue;
|
|
7873
|
+
const body = desired.get(norm);
|
|
7874
|
+
if (body != null) out.push(`## ${heading}
|
|
7875
|
+
|
|
7876
|
+
${body}`);
|
|
7877
|
+
}
|
|
7878
|
+
const preambleText = preambleLines.join("\n").trim();
|
|
7879
|
+
return `${preambleText}
|
|
7880
|
+
|
|
7881
|
+
${out.join("\n\n")}
|
|
7882
|
+
`;
|
|
7883
|
+
}
|
|
7884
|
+
function freshBody(input) {
|
|
7885
|
+
const preamble = `# ${input.title}
|
|
7886
|
+
|
|
7887
|
+
> Type: ${input.type}`;
|
|
7888
|
+
const out = [];
|
|
7889
|
+
const desired = desiredSections(input);
|
|
7890
|
+
for (const heading of SECTION_ORDER) {
|
|
7891
|
+
const body = desired.get(heading.toLowerCase());
|
|
7892
|
+
if (body != null) out.push(`## ${heading}
|
|
7893
|
+
|
|
7894
|
+
${body}`);
|
|
7895
|
+
}
|
|
7896
|
+
return `${preamble}
|
|
7897
|
+
|
|
7898
|
+
${out.join("\n\n")}
|
|
7899
|
+
`.replace(/\n{3,}/g, "\n\n");
|
|
7900
|
+
}
|
|
7901
|
+
function applyFrontmatter(data, input) {
|
|
7902
|
+
const next = { ...data };
|
|
7903
|
+
next.title = input.title;
|
|
7904
|
+
next.type = input.type;
|
|
7905
|
+
next.work_type = input.type;
|
|
7906
|
+
if (input.summary != null) next.summary = input.summary;
|
|
7907
|
+
next.affected_modules = [...input.affectedModules];
|
|
7908
|
+
if (input.scopeConfidence && VALID_CONFIDENCE.has(input.scopeConfidence.level)) {
|
|
7909
|
+
next.scope_confidence = { level: input.scopeConfidence.level, reasons: input.scopeConfidence.reasons.filter((r) => r.trim()) };
|
|
7910
|
+
} else {
|
|
7911
|
+
delete next.scope_confidence;
|
|
7912
|
+
}
|
|
7913
|
+
const coverage = {};
|
|
7914
|
+
for (const c of input.moduleCoverage) {
|
|
7915
|
+
if (!VALID_COVERAGE.has(c.status)) continue;
|
|
7916
|
+
coverage[c.id] = c.reason?.trim() ? { status: c.status, reason: c.reason.trim() } : { status: c.status };
|
|
7917
|
+
}
|
|
7918
|
+
if (Object.keys(coverage).length > 0) next.module_coverage = coverage;
|
|
7919
|
+
else delete next.module_coverage;
|
|
7920
|
+
const surfaces = {};
|
|
7921
|
+
for (const s of input.impactAnalysis) {
|
|
7922
|
+
if (!VALID_COVERAGE.has(s.status)) continue;
|
|
7923
|
+
const entry = { status: s.status };
|
|
7924
|
+
if (s.reason?.trim()) entry.reason = s.reason.trim();
|
|
7925
|
+
if (s.question?.trim()) entry.question = s.question.trim();
|
|
7926
|
+
surfaces[s.surface] = entry;
|
|
7927
|
+
}
|
|
7928
|
+
if (Object.keys(surfaces).length > 0) next.impact_analysis = { surfaces };
|
|
7929
|
+
else delete next.impact_analysis;
|
|
7930
|
+
if (input.decisions.length > 0) next.decisions = [...input.decisions];
|
|
7931
|
+
else delete next.decisions;
|
|
7932
|
+
if (input.relatedKnowledge.length > 0) next.related_knowledge = [...input.relatedKnowledge];
|
|
7933
|
+
else delete next.related_knowledge;
|
|
7934
|
+
return next;
|
|
7935
|
+
}
|
|
7936
|
+
function serialize(data, body) {
|
|
7937
|
+
return matter7.stringify(`
|
|
7938
|
+
${body.trim()}
|
|
7939
|
+
`, data);
|
|
7940
|
+
}
|
|
7941
|
+
function toInput(data, content) {
|
|
7942
|
+
const { sections } = parseBody(content);
|
|
7943
|
+
const sec = (name) => {
|
|
7944
|
+
const s = sections.find((x) => x.normalized === name);
|
|
7945
|
+
return s && s.body.trim() ? s.body.trim() : void 0;
|
|
7946
|
+
};
|
|
7947
|
+
const bullets = (name) => {
|
|
7948
|
+
const body = sec(name);
|
|
7949
|
+
if (!body) return [];
|
|
7950
|
+
return body.split(/\r?\n/).map((l) => l.replace(/^\s*[-*+]\s+(\[[ xX]\]\s*)?/, "").trim()).filter(Boolean);
|
|
7951
|
+
};
|
|
7952
|
+
const criteria = (() => {
|
|
7953
|
+
const body = sec("acceptance criteria");
|
|
7954
|
+
if (!body) return [];
|
|
7955
|
+
const out = [];
|
|
7956
|
+
for (const line of body.split(/\r?\n/)) {
|
|
7957
|
+
const m = line.match(/^\s*[-*+]\s+(.*)$/);
|
|
7958
|
+
if (!m) continue;
|
|
7959
|
+
let text3 = m[1].trim();
|
|
7960
|
+
let checked = null;
|
|
7961
|
+
const cb = text3.match(/^\[([ xX])\]\s*(.*)$/);
|
|
7962
|
+
if (cb) {
|
|
7963
|
+
checked = cb[1].toLowerCase() === "x";
|
|
7964
|
+
text3 = cb[2].trim();
|
|
7965
|
+
}
|
|
7966
|
+
if (text3) out.push({ text: text3, checked });
|
|
7967
|
+
}
|
|
7968
|
+
return out;
|
|
7969
|
+
})();
|
|
7970
|
+
const sc = data.scope_confidence;
|
|
7971
|
+
const mc = data.module_coverage;
|
|
7972
|
+
const ia = data.impact_analysis?.surfaces;
|
|
7973
|
+
return {
|
|
7974
|
+
title: String(data.title ?? ""),
|
|
7975
|
+
type: String(data.type ?? "feature"),
|
|
7976
|
+
summary: data.summary ? String(data.summary) : void 0,
|
|
7977
|
+
actor: sec("actor"),
|
|
7978
|
+
outcome: sec("outcome"),
|
|
7979
|
+
currentBehavior: sec("current behavior"),
|
|
7980
|
+
targetBehavior: sec("target behavior"),
|
|
7981
|
+
entryPoints: sec("entry points"),
|
|
7982
|
+
endToEndFlow: sec("end-to-end flow"),
|
|
7983
|
+
scopeConfidence: sc && sc.level ? { level: String(sc.level), reasons: Array.isArray(sc.reasons) ? sc.reasons.map(String) : [] } : null,
|
|
7984
|
+
scopeUnknowns: bullets("scope unknowns"),
|
|
7985
|
+
affectedModules: Array.isArray(data.affected_modules) ? data.affected_modules.map(String) : [],
|
|
7986
|
+
moduleCoverage: mc ? Object.entries(mc).map(([id, v]) => ({ id, status: String(v.status ?? ""), ...v.reason ? { reason: String(v.reason) } : {} })) : [],
|
|
7987
|
+
impactAnalysis: ia ? Object.entries(ia).map(([surface, v]) => ({ surface, status: String(v.status ?? ""), ...v.reason ? { reason: String(v.reason) } : {}, ...v.question ? { question: String(v.question) } : {} })) : [],
|
|
7988
|
+
acceptanceCriteria: criteria,
|
|
7989
|
+
decisions: Array.isArray(data.decisions) ? data.decisions.map(String) : [],
|
|
7990
|
+
relatedKnowledge: Array.isArray(data.related_knowledge) ? data.related_knowledge.map(String) : []
|
|
7991
|
+
};
|
|
7992
|
+
}
|
|
7993
|
+
function getWorkItemForEdit(dir, id) {
|
|
7994
|
+
const { filePath, relPath } = findArtifact(dir, id);
|
|
7995
|
+
const raw = readFile(filePath);
|
|
7996
|
+
const { data, content } = matter7(raw);
|
|
7997
|
+
const status = lifecycleStateOf({ status: String(data.status ?? ""), filePath });
|
|
7998
|
+
const input = toInput(data, content);
|
|
7999
|
+
const editable = EDITABLE_STATES.includes(status);
|
|
8000
|
+
return {
|
|
8001
|
+
...input,
|
|
8002
|
+
id,
|
|
8003
|
+
status,
|
|
8004
|
+
revision: revisionOf(raw),
|
|
8005
|
+
path: relPath,
|
|
8006
|
+
editable,
|
|
8007
|
+
...editable ? {} : { editableReason: status === "ready" ? "This Work Item is Ready. Reopen it as Draft to edit its scope." : `This Work Item is ${status} and cannot be edited from the structured editor.` }
|
|
8008
|
+
};
|
|
8009
|
+
}
|
|
8010
|
+
var CAPTURE_SECTIONS = [
|
|
8011
|
+
{ field: "problem", heading: "Problem" },
|
|
8012
|
+
{ field: "expected_result", heading: "Expected result" },
|
|
8013
|
+
{ field: "impact", heading: "Impact" },
|
|
8014
|
+
{ field: "acceptance_criteria", heading: "Acceptance criteria", list: true },
|
|
8015
|
+
{ field: "design", heading: "Design" },
|
|
8016
|
+
{ field: "risks", heading: "Risks" }
|
|
8017
|
+
];
|
|
8018
|
+
function captureBody(title, type, answers) {
|
|
8019
|
+
const out = [`# ${title}`, "", `> Type: ${type}`];
|
|
8020
|
+
for (const { field, heading, list: list2 } of CAPTURE_SECTIONS) {
|
|
8021
|
+
const value = answers[field]?.trim();
|
|
8022
|
+
if (!value) continue;
|
|
8023
|
+
if (list2) {
|
|
8024
|
+
const items = value.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).map((l) => /^[-*+]\s/.test(l) ? l : `- ${l}`);
|
|
8025
|
+
out.push("", `## ${heading}`, "", items.join("\n"));
|
|
8026
|
+
} else {
|
|
8027
|
+
out.push("", `## ${heading}`, "", value);
|
|
8028
|
+
}
|
|
8029
|
+
}
|
|
8030
|
+
return out.join("\n") + "\n";
|
|
8031
|
+
}
|
|
8032
|
+
function createWorkItem(dir, opts) {
|
|
8033
|
+
const intent = opts.intent.trim();
|
|
8034
|
+
if (!intent) throw new WorkItemWriteError("INVALID_INPUT", "An intent or summary is required.");
|
|
8035
|
+
const type = opts.type.trim() || "feature";
|
|
8036
|
+
if (!WORK_ITEM_TYPES.has(type)) throw new WorkItemWriteError("INVALID_INPUT", `Unknown Work Item type "${type}".`);
|
|
8037
|
+
const id = nextWorkItemId(dir);
|
|
8038
|
+
const title = intent.split(/\r?\n/)[0].trim().slice(0, 120);
|
|
8039
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
8040
|
+
const data = {
|
|
8041
|
+
type,
|
|
8042
|
+
id,
|
|
8043
|
+
title,
|
|
8044
|
+
status: "draft",
|
|
8045
|
+
work_type: type,
|
|
8046
|
+
created_at: today,
|
|
8047
|
+
source: { type: "manual", inferred: false },
|
|
8048
|
+
generated_by: "kaddo-admin",
|
|
8049
|
+
affected_modules: [],
|
|
8050
|
+
summary: intent
|
|
8051
|
+
};
|
|
8052
|
+
const answers = opts.answers ?? {};
|
|
8053
|
+
const hasAnswers = Object.values(answers).some((v) => v?.trim());
|
|
8054
|
+
const body = hasAnswers ? captureBody(title, type, answers) : freshBody({
|
|
8055
|
+
title,
|
|
8056
|
+
type,
|
|
8057
|
+
summary: intent,
|
|
8058
|
+
scopeUnknowns: [],
|
|
8059
|
+
affectedModules: [],
|
|
8060
|
+
moduleCoverage: [],
|
|
8061
|
+
impactAnalysis: [],
|
|
8062
|
+
acceptanceCriteria: [],
|
|
8063
|
+
decisions: [],
|
|
8064
|
+
relatedKnowledge: [],
|
|
8065
|
+
scopeConfidence: null
|
|
8066
|
+
});
|
|
8067
|
+
const relPath = `${WORK_ITEMS_DIR}/draft/${id}-${slugify2(title)}.md`;
|
|
8068
|
+
const filePath = join(dir, relPath);
|
|
8069
|
+
if (exists(filePath)) throw new WorkItemWriteError("INVALID_INPUT", `Work Item file already exists: ${relPath}`);
|
|
8070
|
+
const raw = serialize(data, body);
|
|
8071
|
+
atomicWrite(filePath, raw);
|
|
8072
|
+
return { id, path: relPath, revision: revisionOf(raw) };
|
|
8073
|
+
}
|
|
8074
|
+
function validateInput(input) {
|
|
8075
|
+
if (!input.title.trim()) throw new WorkItemWriteError("INVALID_INPUT", "Title is required.");
|
|
8076
|
+
if (!WORK_ITEM_TYPES.has(input.type)) throw new WorkItemWriteError("INVALID_INPUT", `Unknown Work Item type "${input.type}".`);
|
|
8077
|
+
}
|
|
8078
|
+
function updateWorkItem(dir, id, input, expectedRevision) {
|
|
8079
|
+
validateInput(input);
|
|
8080
|
+
const { filePath, relPath } = findArtifact(dir, id);
|
|
8081
|
+
const raw = readFile(filePath);
|
|
8082
|
+
if (revisionOf(raw) !== expectedRevision) {
|
|
8083
|
+
throw new WorkItemWriteError("WORK_ITEM_CONFLICT", "This Work Item changed outside Kaddo Admin. Reload the latest version before saving.");
|
|
8084
|
+
}
|
|
8085
|
+
const { data, content } = matter7(raw);
|
|
8086
|
+
const status = lifecycleStateOf({ status: String(data.status ?? ""), filePath });
|
|
8087
|
+
if (!EDITABLE_STATES.includes(status)) {
|
|
8088
|
+
throw new WorkItemWriteError("WORK_ITEM_NOT_EDITABLE", `A ${status} Work Item cannot be edited. Reopen it as Draft first.`);
|
|
8089
|
+
}
|
|
8090
|
+
const nextData = applyFrontmatter(data, input);
|
|
8091
|
+
const nextBody = mergeBody(content, input);
|
|
8092
|
+
const nextRaw = serialize(nextData, nextBody);
|
|
8093
|
+
atomicWrite(filePath, nextRaw);
|
|
8094
|
+
return { revision: revisionOf(nextRaw), path: relPath };
|
|
8095
|
+
}
|
|
8096
|
+
function validateWorkItem(dir, id) {
|
|
8097
|
+
const { filePath } = findArtifact(dir, id);
|
|
8098
|
+
const raw = readFile(filePath);
|
|
8099
|
+
const { data, content } = matter7(raw);
|
|
8100
|
+
const input = toInput(data, content);
|
|
8101
|
+
const findings = [];
|
|
8102
|
+
const modules = validModuleIds(dir);
|
|
8103
|
+
for (const c of input.moduleCoverage) {
|
|
8104
|
+
if (c.status === "affected" && !input.affectedModules.includes(c.id)) {
|
|
8105
|
+
findings.push({ level: "blocking", message: `${c.id} is marked affected in module coverage but is missing from affected_modules.` });
|
|
8106
|
+
}
|
|
8107
|
+
}
|
|
8108
|
+
for (const m of input.affectedModules) {
|
|
8109
|
+
if (!modules.has(m)) findings.push({ level: "blocking", message: `Module "${m}" is not registered in this project.` });
|
|
8110
|
+
}
|
|
8111
|
+
if (!input.targetBehavior?.trim()) findings.push({ level: "warning", message: "Target behavior is not defined." });
|
|
8112
|
+
if (input.acceptanceCriteria.length === 0) findings.push({ level: "warning", message: "No acceptance criteria have been defined." });
|
|
8113
|
+
if (input.scopeConfidence?.level === "low") findings.push({ level: "warning", message: "Scope confidence is Low." });
|
|
8114
|
+
if (!input.scopeConfidence) findings.push({ level: "warning", message: "Scope confidence has not been assessed." });
|
|
8115
|
+
for (const c of input.moduleCoverage) {
|
|
8116
|
+
if (c.status === "reviewed-not-affected") findings.push({ level: "fyi", message: `${c.id} was reviewed and is not affected.` });
|
|
8117
|
+
}
|
|
8118
|
+
for (const s of input.impactAnalysis) {
|
|
8119
|
+
if (s.status === "unknown") findings.push({ level: "fyi", message: `Impact on ${s.surface} is unknown.` });
|
|
8120
|
+
}
|
|
8121
|
+
const canMarkReady = !findings.some((f) => f.level === "blocking");
|
|
8122
|
+
return { findings, canMarkReady };
|
|
8123
|
+
}
|
|
8124
|
+
var ALLOWED = { draft: ["ready"], ready: ["draft"] };
|
|
8125
|
+
function transitionWorkItem(dir, id, to, expectedRevision) {
|
|
8126
|
+
const { filePath } = findArtifact(dir, id);
|
|
8127
|
+
const raw = readFile(filePath);
|
|
8128
|
+
if (revisionOf(raw) !== expectedRevision) {
|
|
8129
|
+
throw new WorkItemWriteError("WORK_ITEM_CONFLICT", "This Work Item changed outside Kaddo Admin. Reload the latest version before continuing.");
|
|
8130
|
+
}
|
|
8131
|
+
const { data, content } = matter7(raw);
|
|
8132
|
+
const from = lifecycleStateOf({ status: String(data.status ?? ""), filePath });
|
|
8133
|
+
if (!(ALLOWED[from] ?? []).includes(to)) {
|
|
8134
|
+
throw new WorkItemWriteError("INVALID_TRANSITION", `Cannot transition a ${from} Work Item to ${to}.`);
|
|
8135
|
+
}
|
|
8136
|
+
if (to === "ready") {
|
|
8137
|
+
const { canMarkReady } = validateWorkItem(dir, id);
|
|
8138
|
+
if (!canMarkReady) throw new WorkItemWriteError("INVALID_TRANSITION", "This Work Item has blocking issues and cannot be marked Ready.");
|
|
8139
|
+
}
|
|
8140
|
+
const nextData = { ...data, status: to };
|
|
8141
|
+
const nextRaw = serialize(nextData, content);
|
|
8142
|
+
const filename = path2.basename(filePath);
|
|
8143
|
+
const targetRel = `${WORK_ITEMS_DIR}/${to}/${filename}`;
|
|
8144
|
+
const targetPath = join(dir, targetRel);
|
|
8145
|
+
atomicWrite(targetPath, nextRaw);
|
|
8146
|
+
if (path2.resolve(targetPath) !== path2.resolve(filePath)) {
|
|
8147
|
+
try {
|
|
8148
|
+
fs2.rmSync(filePath, { force: true });
|
|
8149
|
+
} catch {
|
|
8150
|
+
}
|
|
8151
|
+
}
|
|
8152
|
+
return { revision: revisionOf(nextRaw), status: to, path: targetRel };
|
|
8153
|
+
}
|
|
8154
|
+
|
|
8155
|
+
// src/core/knowledge-levels.ts
|
|
8156
|
+
var WORK_ITEM_TYPES2 = ["feature", "bugfix", "hotfix", "spike", "chore"];
|
|
8157
|
+
var LEVELS = {
|
|
8158
|
+
K0: {
|
|
8159
|
+
level: "K0",
|
|
8160
|
+
description: "Trivial change. No formal knowledge required.",
|
|
8161
|
+
questions: [],
|
|
8162
|
+
qualityGate: []
|
|
8163
|
+
},
|
|
8164
|
+
K1: {
|
|
8165
|
+
level: "K1",
|
|
8166
|
+
description: "Simple fix or hotfix.",
|
|
8167
|
+
questions: [
|
|
8168
|
+
{
|
|
8169
|
+
id: "problem",
|
|
8170
|
+
prompt: "What problem does this fix?",
|
|
8171
|
+
placeholder: "e.g. Button is not clickable on mobile",
|
|
8172
|
+
frontMatterField: "problem",
|
|
8173
|
+
required: true
|
|
8174
|
+
},
|
|
8175
|
+
{
|
|
8176
|
+
id: "expected_result",
|
|
8177
|
+
prompt: "What is the expected result after the fix?",
|
|
8178
|
+
placeholder: "e.g. Button works correctly on all screen sizes",
|
|
8179
|
+
frontMatterField: "expected_result",
|
|
8180
|
+
required: true
|
|
8181
|
+
}
|
|
8182
|
+
],
|
|
8183
|
+
qualityGate: ["Problem is clear.", "Expected result is defined."]
|
|
8184
|
+
},
|
|
8185
|
+
K2: {
|
|
8186
|
+
level: "K2",
|
|
8187
|
+
description: "Feature or bugfix with functional impact.",
|
|
8188
|
+
questions: [
|
|
8189
|
+
{
|
|
8190
|
+
id: "problem",
|
|
8191
|
+
prompt: "What problem does this solve?",
|
|
8192
|
+
placeholder: "e.g. Users cannot complete checkout without email verification",
|
|
8193
|
+
frontMatterField: "problem",
|
|
8194
|
+
required: true
|
|
8195
|
+
},
|
|
8196
|
+
{
|
|
8197
|
+
id: "expected_result",
|
|
8198
|
+
prompt: "What is the expected result?",
|
|
8199
|
+
placeholder: "e.g. Users can verify email inline during checkout",
|
|
8200
|
+
frontMatterField: "expected_result",
|
|
8201
|
+
required: true
|
|
8202
|
+
},
|
|
8203
|
+
{
|
|
8204
|
+
id: "impact",
|
|
8205
|
+
prompt: "What is the impact if this is not done?",
|
|
8206
|
+
placeholder: "e.g. ~30% of users drop off at checkout",
|
|
8207
|
+
frontMatterField: "impact",
|
|
8208
|
+
required: true
|
|
8209
|
+
},
|
|
8210
|
+
{
|
|
8211
|
+
id: "acceptance_criteria",
|
|
8212
|
+
prompt: "What are the acceptance criteria? (one per line, press Enter twice to finish)",
|
|
8213
|
+
placeholder: "e.g. Email is verified before payment step",
|
|
8214
|
+
frontMatterField: "acceptance_criteria",
|
|
8215
|
+
required: true
|
|
8216
|
+
}
|
|
8217
|
+
],
|
|
8218
|
+
qualityGate: [
|
|
8219
|
+
"Problem is clear.",
|
|
8220
|
+
"Expected result is defined.",
|
|
8221
|
+
"Impact of not doing it is stated.",
|
|
8222
|
+
"Acceptance criteria are verifiable."
|
|
8223
|
+
]
|
|
8224
|
+
},
|
|
8225
|
+
K3: {
|
|
8226
|
+
level: "K3",
|
|
8227
|
+
description: "Capability, integration, or significant functional change.",
|
|
8228
|
+
questions: [
|
|
8229
|
+
{
|
|
8230
|
+
id: "problem",
|
|
8231
|
+
prompt: "What problem does this solve?",
|
|
8232
|
+
placeholder: "e.g. Payments need to support multiple providers",
|
|
8233
|
+
frontMatterField: "problem",
|
|
8234
|
+
required: true
|
|
8235
|
+
},
|
|
8236
|
+
{
|
|
8237
|
+
id: "impact",
|
|
8238
|
+
prompt: "What is the impact if this is not done?",
|
|
8239
|
+
placeholder: "e.g. We are locked into a single provider with no fallback",
|
|
8240
|
+
frontMatterField: "impact",
|
|
8241
|
+
required: true
|
|
8242
|
+
},
|
|
8243
|
+
{
|
|
8244
|
+
id: "acceptance_criteria",
|
|
8245
|
+
prompt: "What are the acceptance criteria?",
|
|
8246
|
+
placeholder: "e.g. System routes to provider B when provider A fails",
|
|
8247
|
+
frontMatterField: "acceptance_criteria",
|
|
8248
|
+
required: true
|
|
8249
|
+
},
|
|
8250
|
+
{
|
|
8251
|
+
id: "design",
|
|
8252
|
+
prompt: "What is the proposed design or approach?",
|
|
8253
|
+
placeholder: "e.g. Abstract provider interface + strategy pattern",
|
|
8254
|
+
frontMatterField: "design",
|
|
8255
|
+
required: true
|
|
8256
|
+
}
|
|
8257
|
+
],
|
|
8258
|
+
qualityGate: [
|
|
8259
|
+
"Problem is clear.",
|
|
8260
|
+
"Impact is stated.",
|
|
8261
|
+
"Acceptance criteria are verifiable.",
|
|
8262
|
+
"Design is sufficient to start."
|
|
8263
|
+
]
|
|
8264
|
+
},
|
|
8265
|
+
K4: {
|
|
8266
|
+
level: "K4",
|
|
8267
|
+
description: "Architecture change, migration, or high-risk decision.",
|
|
8268
|
+
questions: [
|
|
8269
|
+
{
|
|
8270
|
+
id: "problem",
|
|
8271
|
+
prompt: "What problem does this solve?",
|
|
8272
|
+
placeholder: "e.g. Current auth system does not support SSO",
|
|
8273
|
+
frontMatterField: "problem",
|
|
8274
|
+
required: true
|
|
8275
|
+
},
|
|
8276
|
+
{
|
|
8277
|
+
id: "impact",
|
|
8278
|
+
prompt: "What is the impact if this is not done?",
|
|
8279
|
+
placeholder: "e.g. Enterprise clients cannot onboard",
|
|
8280
|
+
frontMatterField: "impact",
|
|
8281
|
+
required: true
|
|
8282
|
+
},
|
|
8283
|
+
{
|
|
8284
|
+
id: "design",
|
|
8285
|
+
prompt: "What is the proposed design or approach?",
|
|
8286
|
+
placeholder: "e.g. Replace JWT-only auth with OIDC + JWT hybrid",
|
|
8287
|
+
frontMatterField: "design",
|
|
8288
|
+
required: true
|
|
8289
|
+
},
|
|
8290
|
+
{
|
|
8291
|
+
id: "risks",
|
|
8292
|
+
prompt: "What are the main risks?",
|
|
8293
|
+
placeholder: "e.g. Existing sessions may be invalidated during migration",
|
|
8294
|
+
frontMatterField: "risks",
|
|
8295
|
+
required: true
|
|
8296
|
+
}
|
|
8297
|
+
],
|
|
8298
|
+
qualityGate: [
|
|
8299
|
+
"Problem is clear.",
|
|
8300
|
+
"Impact is stated.",
|
|
8301
|
+
"Design is documented.",
|
|
8302
|
+
"Risks are identified.",
|
|
8303
|
+
"ADR is created or linked if applicable."
|
|
8304
|
+
]
|
|
8305
|
+
}
|
|
8306
|
+
};
|
|
8307
|
+
var TYPE_TO_LEVEL = {
|
|
8308
|
+
hotfix: "K1",
|
|
8309
|
+
bugfix: "K2",
|
|
8310
|
+
feature: "K2",
|
|
8311
|
+
spike: "K3",
|
|
8312
|
+
// Chores are maintenance/config/tooling work — low ceremony (problem + expected result).
|
|
8313
|
+
chore: "K1"
|
|
8314
|
+
};
|
|
8315
|
+
function getLevel(level) {
|
|
8316
|
+
return LEVELS[level];
|
|
8317
|
+
}
|
|
8318
|
+
function getLevelForType(type) {
|
|
8319
|
+
return TYPE_TO_LEVEL[type];
|
|
8320
|
+
}
|
|
8321
|
+
|
|
8322
|
+
// src/core/work-item-refinement.ts
|
|
8323
|
+
function toCapture(q) {
|
|
8324
|
+
return { id: q.id, prompt: q.prompt, placeholder: q.placeholder, field: q.frontMatterField, required: q.required };
|
|
8325
|
+
}
|
|
8326
|
+
function getWorkItemCaptureDefinition() {
|
|
8327
|
+
const questions = {};
|
|
8328
|
+
for (const type of WORK_ITEM_TYPES2) {
|
|
8329
|
+
questions[type] = getLevel(getLevelForType(type)).questions.map(toCapture);
|
|
8330
|
+
}
|
|
8331
|
+
return {
|
|
8332
|
+
types: WORK_ITEM_TYPES2.map((t) => ({ value: t, label: t.charAt(0).toUpperCase() + t.slice(1) })),
|
|
8333
|
+
questions
|
|
8334
|
+
};
|
|
8335
|
+
}
|
|
8336
|
+
function getWorkItemAgentAssets() {
|
|
8337
|
+
const agent = AGENT_PROMPTS.find((p2) => p2.fileName === "work-item-agent.md");
|
|
8338
|
+
const skill2 = skillById("work-item-refinement");
|
|
8339
|
+
return { agentPrompt: agent?.content ?? "", skill: skill2?.content ?? null };
|
|
8340
|
+
}
|
|
8341
|
+
function assembleRefinementContext(dir, workItemId) {
|
|
8342
|
+
const edit = getWorkItemForEdit(dir, workItemId);
|
|
8343
|
+
const config = loadConfig(dir);
|
|
8344
|
+
const knowledgeArtifacts = discoverKnowledge(dir).filter(
|
|
8345
|
+
(a) => !a.isWorkItem && a.type !== "skill" && a.type !== "agent" && a.layer !== "unknown"
|
|
8346
|
+
);
|
|
8347
|
+
const decisions = knowledgeArtifacts.filter((a) => a.type === "adr" || /^adr-/i.test(a.id)).map((a) => ({ id: a.id, title: a.title || a.id }));
|
|
8348
|
+
const knowledge = knowledgeArtifacts.filter((a) => !(a.type === "adr" || /^adr-/i.test(a.id))).map((a) => ({ id: a.id || a.relPath, title: a.title || a.id, layer: a.layer, type: a.type || void 0, summary: a.summary || void 0 }));
|
|
8349
|
+
const modules = ["core", ...loadMappedModules(dir).map((m) => m.id)].filter((v, i, arr) => arr.indexOf(v) === i);
|
|
8350
|
+
return {
|
|
8351
|
+
workItem: { id: edit.id, title: edit.title, status: edit.status, intent: edit.summary ?? edit.title, current: stripEdit(edit) },
|
|
8352
|
+
project: { name: config?.project.name ?? "unknown", state: config?.project.state ?? "unknown", structure: config?.project.structure ?? "unknown" },
|
|
8353
|
+
modules,
|
|
8354
|
+
knowledge,
|
|
8355
|
+
decisions,
|
|
8356
|
+
revision: edit.revision
|
|
8357
|
+
};
|
|
8358
|
+
}
|
|
8359
|
+
function stripEdit(edit) {
|
|
8360
|
+
const { id: _i, status: _s, revision: _r, path: _p, editable: _e, editableReason: _er, ...input } = edit;
|
|
8361
|
+
return input;
|
|
8362
|
+
}
|
|
8363
|
+
var VALID_COVERAGE2 = /* @__PURE__ */ new Set(["affected", "reviewed-not-affected", "unknown", "not-applicable"]);
|
|
8364
|
+
var VALID_CONFIDENCE2 = /* @__PURE__ */ new Set(["high", "medium", "low"]);
|
|
8365
|
+
function str(v) {
|
|
8366
|
+
return typeof v === "string" && v.trim() ? v.trim() : void 0;
|
|
8367
|
+
}
|
|
8368
|
+
function strList(v) {
|
|
8369
|
+
return Array.isArray(v) ? v.map((x) => typeof x === "string" ? x.trim() : "").filter(Boolean) : [];
|
|
8370
|
+
}
|
|
8371
|
+
function normalizeAndValidateProposal(dir, workItemId, proposal) {
|
|
8372
|
+
const ctx = assembleRefinementContext(dir, workItemId);
|
|
8373
|
+
const knownModules = new Set(ctx.modules);
|
|
8374
|
+
const knownDecisions = new Set(ctx.decisions.map((d) => d.id));
|
|
8375
|
+
const knownKnowledge = new Set(ctx.knowledge.map((k) => k.id));
|
|
8376
|
+
const extraFindings = [];
|
|
8377
|
+
const input = { ...ctx.workItem.current };
|
|
8378
|
+
if (str(proposal.title)) input.title = str(proposal.title);
|
|
8379
|
+
const o = proposal.outcome ?? {};
|
|
8380
|
+
if (str(o.actor) !== void 0) input.actor = str(o.actor);
|
|
8381
|
+
if (str(o.observableOutcome) !== void 0) input.outcome = str(o.observableOutcome);
|
|
8382
|
+
if (str(o.currentBehavior) !== void 0) input.currentBehavior = str(o.currentBehavior);
|
|
8383
|
+
if (str(o.targetBehavior) !== void 0) input.targetBehavior = str(o.targetBehavior);
|
|
8384
|
+
const j = proposal.journey ?? {};
|
|
8385
|
+
if (j.entryPoints) input.entryPoints = strList(j.entryPoints).join("\n");
|
|
8386
|
+
if (j.flow) input.endToEndFlow = strList(j.flow).map((s) => `- ${s}`).join("\n");
|
|
8387
|
+
if (proposal.moduleCoverage) {
|
|
8388
|
+
input.moduleCoverage = proposal.moduleCoverage.filter((c) => {
|
|
8389
|
+
if (!knownModules.has(c.id)) {
|
|
8390
|
+
extraFindings.push({ level: "warning", message: `Proposed module "${c.id}" is not registered and was not included.` });
|
|
8391
|
+
return false;
|
|
8392
|
+
}
|
|
8393
|
+
return VALID_COVERAGE2.has(c.status);
|
|
8394
|
+
}).map((c) => ({ id: c.id, status: c.status, ...str(c.reason) ? { reason: str(c.reason) } : {} }));
|
|
8395
|
+
}
|
|
8396
|
+
const affected = /* @__PURE__ */ new Set();
|
|
8397
|
+
for (const m of proposal.affectedModules ?? []) if (knownModules.has(m)) affected.add(m);
|
|
8398
|
+
for (const c of input.moduleCoverage) if (c.status === "affected") affected.add(c.id);
|
|
8399
|
+
if (proposal.affectedModules || proposal.moduleCoverage) input.affectedModules = [...affected];
|
|
8400
|
+
if (proposal.impactAnalysis) {
|
|
8401
|
+
input.impactAnalysis = proposal.impactAnalysis.filter((s) => VALID_COVERAGE2.has(s.status) && str(s.surface)).map((s) => ({ surface: str(s.surface), status: s.status, ...str(s.reason) ? { reason: str(s.reason) } : {}, ...str(s.question) ? { question: str(s.question) } : {} }));
|
|
8402
|
+
}
|
|
8403
|
+
if (proposal.scopeConfidence && VALID_CONFIDENCE2.has(proposal.scopeConfidence.level)) {
|
|
8404
|
+
input.scopeConfidence = { level: proposal.scopeConfidence.level, reasons: strList(proposal.scopeConfidence.reasons) };
|
|
8405
|
+
}
|
|
8406
|
+
if (proposal.scopeUnknowns) input.scopeUnknowns = strList(proposal.scopeUnknowns);
|
|
8407
|
+
if (proposal.acceptanceCriteria) input.acceptanceCriteria = strList(proposal.acceptanceCriteria).map((t) => ({ text: t, checked: null }));
|
|
8408
|
+
if (proposal.linkedDecisions) {
|
|
8409
|
+
input.decisions = proposal.linkedDecisions.filter((id) => {
|
|
8410
|
+
if (!knownDecisions.has(id)) {
|
|
8411
|
+
extraFindings.push({ level: "warning", message: `Proposed decision "${id}" does not exist and was not linked.` });
|
|
8412
|
+
return false;
|
|
8413
|
+
}
|
|
8414
|
+
return true;
|
|
8415
|
+
});
|
|
8416
|
+
}
|
|
8417
|
+
if (proposal.relatedKnowledge) {
|
|
8418
|
+
input.relatedKnowledge = proposal.relatedKnowledge.filter((id) => {
|
|
8419
|
+
if (!knownKnowledge.has(id)) {
|
|
8420
|
+
extraFindings.push({ level: "warning", message: `Proposed knowledge "${id}" could not be resolved and was not linked.` });
|
|
8421
|
+
return false;
|
|
8422
|
+
}
|
|
8423
|
+
return true;
|
|
8424
|
+
});
|
|
8425
|
+
}
|
|
8426
|
+
const findings = [...extraFindings, ...evaluate(input, knownModules)];
|
|
8427
|
+
const blocking = findings.filter((f) => f.level === "blocking").length;
|
|
8428
|
+
const warning = findings.filter((f) => f.level === "warning").length;
|
|
8429
|
+
const fyi = findings.filter((f) => f.level === "fyi").length;
|
|
8430
|
+
return { input, validation: { findings, blocking, warning, fyi, canApply: true } };
|
|
8431
|
+
}
|
|
8432
|
+
function evaluate(input, knownModules) {
|
|
8433
|
+
const findings = [];
|
|
8434
|
+
for (const c of input.moduleCoverage) {
|
|
8435
|
+
if (c.status === "affected" && !input.affectedModules.includes(c.id)) {
|
|
8436
|
+
findings.push({ level: "blocking", message: `${c.id} is marked affected in module coverage but is missing from affected_modules.` });
|
|
8437
|
+
}
|
|
8438
|
+
}
|
|
8439
|
+
for (const m of input.affectedModules) {
|
|
8440
|
+
if (!knownModules.has(m)) findings.push({ level: "blocking", message: `Module "${m}" is not registered in this project.` });
|
|
8441
|
+
}
|
|
8442
|
+
if (!input.targetBehavior?.trim()) findings.push({ level: "warning", message: "Target behavior is not defined." });
|
|
8443
|
+
if (input.acceptanceCriteria.length === 0) findings.push({ level: "warning", message: "No acceptance criteria have been defined." });
|
|
8444
|
+
if (input.scopeConfidence?.level === "low") findings.push({ level: "warning", message: "Scope confidence is Low." });
|
|
8445
|
+
if (!input.scopeConfidence) findings.push({ level: "warning", message: "Scope confidence has not been assessed." });
|
|
8446
|
+
for (const s of input.impactAnalysis) if (s.status === "unknown") findings.push({ level: "fyi", message: `Impact on ${s.surface} is unknown.` });
|
|
8447
|
+
return findings;
|
|
8448
|
+
}
|
|
8449
|
+
function applyRefinement(dir, workItemId, proposal, expectedRevision) {
|
|
8450
|
+
const { input } = normalizeAndValidateProposal(dir, workItemId, proposal);
|
|
8451
|
+
return updateWorkItem(dir, workItemId, input, expectedRevision);
|
|
8452
|
+
}
|
|
7718
8453
|
export {
|
|
7719
8454
|
WorkItemNotFoundError,
|
|
8455
|
+
WorkItemWriteError,
|
|
7720
8456
|
analyzeCrossRepoEvidence,
|
|
7721
8457
|
analyzeScopeCoverage,
|
|
8458
|
+
applyRefinement,
|
|
8459
|
+
assembleRefinementContext,
|
|
7722
8460
|
buildProjectExplanation,
|
|
7723
8461
|
buildProjectRoute,
|
|
7724
8462
|
buildReadinessReport,
|
|
8463
|
+
createWorkItem,
|
|
7725
8464
|
cwd,
|
|
7726
8465
|
discoverKnowledge,
|
|
7727
8466
|
discoverWorkItems,
|
|
7728
8467
|
exists,
|
|
7729
8468
|
getWorkItem,
|
|
8469
|
+
getWorkItemAgentAssets,
|
|
8470
|
+
getWorkItemCaptureDefinition,
|
|
8471
|
+
getWorkItemForEdit,
|
|
7730
8472
|
getWorkItems,
|
|
7731
8473
|
getWorkItemsSummary,
|
|
7732
8474
|
isActiveState,
|
|
@@ -7737,5 +8479,9 @@ export {
|
|
|
7737
8479
|
lifecycleStateOf,
|
|
7738
8480
|
loadConfig,
|
|
7739
8481
|
loadMappedModules,
|
|
7740
|
-
|
|
8482
|
+
normalizeAndValidateProposal,
|
|
8483
|
+
readFile,
|
|
8484
|
+
transitionWorkItem,
|
|
8485
|
+
updateWorkItem,
|
|
8486
|
+
validateWorkItem
|
|
7741
8487
|
};
|