@thanh01.pmt/domain-kit 0.1.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/README.md +1101 -0
- package/dist/assembly/index.cjs +213 -0
- package/dist/assembly/index.cjs.map +1 -0
- package/dist/assembly/index.d.cts +66 -0
- package/dist/assembly/index.d.ts +66 -0
- package/dist/assembly/index.mjs +4 -0
- package/dist/assembly/index.mjs.map +1 -0
- package/dist/chunk-3H2HX7PR.mjs +324 -0
- package/dist/chunk-3H2HX7PR.mjs.map +1 -0
- package/dist/chunk-3VVPSRAM.mjs +1297 -0
- package/dist/chunk-3VVPSRAM.mjs.map +1 -0
- package/dist/chunk-DRVOV5ZD.mjs +314 -0
- package/dist/chunk-DRVOV5ZD.mjs.map +1 -0
- package/dist/chunk-F4RZNBOE.mjs +3 -0
- package/dist/chunk-F4RZNBOE.mjs.map +1 -0
- package/dist/chunk-GEUDHZE7.mjs +446 -0
- package/dist/chunk-GEUDHZE7.mjs.map +1 -0
- package/dist/chunk-HH4UX65C.mjs +100 -0
- package/dist/chunk-HH4UX65C.mjs.map +1 -0
- package/dist/chunk-KMYENICQ.mjs +3 -0
- package/dist/chunk-KMYENICQ.mjs.map +1 -0
- package/dist/chunk-KNDBODUQ.mjs +153 -0
- package/dist/chunk-KNDBODUQ.mjs.map +1 -0
- package/dist/chunk-MM2RWYNW.mjs +3 -0
- package/dist/chunk-MM2RWYNW.mjs.map +1 -0
- package/dist/chunk-MOKNFLUD.mjs +851 -0
- package/dist/chunk-MOKNFLUD.mjs.map +1 -0
- package/dist/chunk-ONQHJ4OH.mjs +211 -0
- package/dist/chunk-ONQHJ4OH.mjs.map +1 -0
- package/dist/chunk-PM42MMDJ.mjs +516 -0
- package/dist/chunk-PM42MMDJ.mjs.map +1 -0
- package/dist/chunk-RKRNOVKO.mjs +166 -0
- package/dist/chunk-RKRNOVKO.mjs.map +1 -0
- package/dist/chunk-VFART56O.mjs +884 -0
- package/dist/chunk-VFART56O.mjs.map +1 -0
- package/dist/chunk-XMHKWHVK.mjs +218 -0
- package/dist/chunk-XMHKWHVK.mjs.map +1 -0
- package/dist/conceptEscalator-YfMKIKCZ.d.ts +40 -0
- package/dist/conceptEscalator-ltRLtPhf.d.cts +40 -0
- package/dist/concepts/index.cjs +155 -0
- package/dist/concepts/index.cjs.map +1 -0
- package/dist/concepts/index.d.cts +79 -0
- package/dist/concepts/index.d.ts +79 -0
- package/dist/concepts/index.mjs +4 -0
- package/dist/concepts/index.mjs.map +1 -0
- package/dist/curriculumFeedSchema-TEeQg2bY.d.cts +549 -0
- package/dist/curriculumFeedSchema-TEeQg2bY.d.ts +549 -0
- package/dist/detector/index.cjs +889 -0
- package/dist/detector/index.cjs.map +1 -0
- package/dist/detector/index.d.cts +112 -0
- package/dist/detector/index.d.ts +112 -0
- package/dist/detector/index.mjs +3 -0
- package/dist/detector/index.mjs.map +1 -0
- package/dist/domainProfileSchema-CwT3Ffsw.d.cts +105 -0
- package/dist/domainProfileSchema-CwT3Ffsw.d.ts +105 -0
- package/dist/extractors/index.cjs +387 -0
- package/dist/extractors/index.cjs.map +1 -0
- package/dist/extractors/index.d.cts +46 -0
- package/dist/extractors/index.d.ts +46 -0
- package/dist/extractors/index.mjs +4 -0
- package/dist/extractors/index.mjs.map +1 -0
- package/dist/feed/index.cjs +421 -0
- package/dist/feed/index.cjs.map +1 -0
- package/dist/feed/index.d.cts +23 -0
- package/dist/feed/index.d.ts +23 -0
- package/dist/feed/index.mjs +4 -0
- package/dist/feed/index.mjs.map +1 -0
- package/dist/graph/index.cjs +2167 -0
- package/dist/graph/index.cjs.map +1 -0
- package/dist/graph/index.d.cts +226 -0
- package/dist/graph/index.d.ts +226 -0
- package/dist/graph/index.mjs +4 -0
- package/dist/graph/index.mjs.map +1 -0
- package/dist/graphVerifier-DsTP9uAN.d.ts +37 -0
- package/dist/graphVerifier-QjgDAJce.d.cts +37 -0
- package/dist/hybridGraphSchema-BCgXicgA.d.cts +2840 -0
- package/dist/hybridGraphSchema-BCgXicgA.d.ts +2840 -0
- package/dist/index.cjs +5539 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +18 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.mjs +17 -0
- package/dist/index.mjs.map +1 -0
- package/dist/keywordExtractor-CKaXqSku.d.ts +34 -0
- package/dist/keywordExtractor-zPAz2isq.d.cts +34 -0
- package/dist/llmClient-ysPhLjcH.d.cts +16 -0
- package/dist/llmClient-ysPhLjcH.d.ts +16 -0
- package/dist/parsers/index.cjs +323 -0
- package/dist/parsers/index.cjs.map +1 -0
- package/dist/parsers/index.d.cts +98 -0
- package/dist/parsers/index.d.ts +98 -0
- package/dist/parsers/index.mjs +4 -0
- package/dist/parsers/index.mjs.map +1 -0
- package/dist/pipeline/index.cjs +2166 -0
- package/dist/pipeline/index.cjs.map +1 -0
- package/dist/pipeline/index.d.cts +45 -0
- package/dist/pipeline/index.d.ts +45 -0
- package/dist/pipeline/index.mjs +8 -0
- package/dist/pipeline/index.mjs.map +1 -0
- package/dist/projectGraphSchema-DnD7orZV.d.cts +2581 -0
- package/dist/projectGraphSchema-DnD7orZV.d.ts +2581 -0
- package/dist/schemas/index.cjs +675 -0
- package/dist/schemas/index.cjs.map +1 -0
- package/dist/schemas/index.d.cts +253 -0
- package/dist/schemas/index.d.ts +253 -0
- package/dist/schemas/index.mjs +4 -0
- package/dist/schemas/index.mjs.map +1 -0
- package/package.json +71 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { validateCurriculumFeed } from './chunk-HH4UX65C.mjs';
|
|
2
|
+
|
|
3
|
+
// src/feed/curriculumFeedEmitter.ts
|
|
4
|
+
var BLOOM_RANK = { Remember: 0, Understand: 1, Apply: 2, Analyze: 3, Evaluate: 4, Create: 5 };
|
|
5
|
+
var pad2 = (n) => String(n).padStart(2, "0");
|
|
6
|
+
var arr = (v) => Array.isArray(v) ? v : [];
|
|
7
|
+
var str = (v, d = "") => typeof v === "string" ? v : d;
|
|
8
|
+
var num = (v, d) => typeof v === "number" && Number.isFinite(v) ? v : d;
|
|
9
|
+
var strs = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
|
|
10
|
+
function stepMinutes(step) {
|
|
11
|
+
const effort = step.effort ?? {};
|
|
12
|
+
return Math.max(0, Math.round(num(effort.estimated_minutes, num(step.estimated_minutes, 45))));
|
|
13
|
+
}
|
|
14
|
+
function newKeywordsInOrder(nodes) {
|
|
15
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16
|
+
for (const n of nodes) {
|
|
17
|
+
const all = n.keywords.all;
|
|
18
|
+
n.keywords.new = [];
|
|
19
|
+
n.keywords.prerequisite = [];
|
|
20
|
+
for (const k of all) {
|
|
21
|
+
if (seen.has(k)) n.keywords.prerequisite.push(k);
|
|
22
|
+
else n.keywords.new.push(k);
|
|
23
|
+
seen.add(k);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function feedNodeFromStep(step, featureId) {
|
|
28
|
+
const id = str(step.id);
|
|
29
|
+
return {
|
|
30
|
+
id,
|
|
31
|
+
kind: "product_step",
|
|
32
|
+
name: str(step.name, id),
|
|
33
|
+
concept_codes: strs(step.concept_codes),
|
|
34
|
+
keywords: { all: strs(step.keywords), new: [], prerequisite: [] },
|
|
35
|
+
estimated_minutes: stepMinutes(step),
|
|
36
|
+
bloom_hint: "Apply",
|
|
37
|
+
depth_hint: null,
|
|
38
|
+
completion_level: "standard",
|
|
39
|
+
scaffolding: { scaffold_candidates: [], core_value_minutes: stepMinutes(step) },
|
|
40
|
+
references: arr(step.files).map((f) => ({ file: str(f), evidence: "step file" })).filter((f) => f.file),
|
|
41
|
+
feature_id: featureId,
|
|
42
|
+
concept_ref: null,
|
|
43
|
+
phase_id: "",
|
|
44
|
+
introduce_aspect: null
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function feedNodeFromConcept(concept) {
|
|
48
|
+
const id = str(concept.id);
|
|
49
|
+
const pts = arr(concept.problem_types).map((p) => str(p.bloom_level, "Understand")).filter((b) => b in BLOOM_RANK).sort((a, b) => BLOOM_RANK[a] - BLOOM_RANK[b]);
|
|
50
|
+
const topBloom = pts[pts.length - 1] ?? "Understand";
|
|
51
|
+
return {
|
|
52
|
+
id,
|
|
53
|
+
kind: "concept",
|
|
54
|
+
name: str(concept.name, id),
|
|
55
|
+
concept_codes: [str(concept.code) || id].filter(Boolean),
|
|
56
|
+
keywords: { all: strs(concept.keywords), new: [], prerequisite: [] },
|
|
57
|
+
estimated_minutes: Math.max(0, Math.round(num(concept.estimated_minutes, 30))),
|
|
58
|
+
bloom_hint: topBloom,
|
|
59
|
+
depth_hint: null,
|
|
60
|
+
completion_level: "standard",
|
|
61
|
+
scaffolding: { scaffold_candidates: [], core_value_minutes: 0 },
|
|
62
|
+
references: [],
|
|
63
|
+
feature_id: null,
|
|
64
|
+
concept_ref: id,
|
|
65
|
+
phase_id: "",
|
|
66
|
+
introduce_aspect: null
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function addFeatureGroup(f, fid, nodes, edges, groupings) {
|
|
70
|
+
const steps = arr(f.steps);
|
|
71
|
+
const stepIds = [];
|
|
72
|
+
let prev = "";
|
|
73
|
+
for (let i = 0; i < steps.length; i++) {
|
|
74
|
+
const stepNode = feedNodeFromStep(steps[i], fid);
|
|
75
|
+
if (!stepNode.id) continue;
|
|
76
|
+
stepIds.push(stepNode.id);
|
|
77
|
+
nodes.push(stepNode);
|
|
78
|
+
if (prev) edges.push({ from: prev, to: stepNode.id, kind: "task", reason: "feature build order" });
|
|
79
|
+
prev = stepNode.id;
|
|
80
|
+
}
|
|
81
|
+
for (const dep of strs(f.depends_on)) {
|
|
82
|
+
const depFirst = nodes.find((n) => n.feature_id === dep);
|
|
83
|
+
if (depFirst && stepIds[0]) edges.push({ from: depFirst.id, to: stepIds[0], kind: "task", reason: "feature depends_on " + dep });
|
|
84
|
+
}
|
|
85
|
+
if (stepIds.length > 0) groupings.push({ id: "G-" + fid, name: str(f.name, fid), node_ids: stepIds, rationale: str(f.description, "") });
|
|
86
|
+
return stepIds;
|
|
87
|
+
}
|
|
88
|
+
function emitFromHybrid(hybrid, opts) {
|
|
89
|
+
const nodes = [];
|
|
90
|
+
const edges = [];
|
|
91
|
+
const groupings = [];
|
|
92
|
+
const pg = hybrid.project_graph ?? {};
|
|
93
|
+
const kg = hybrid.knowledge_graph ?? {};
|
|
94
|
+
const rawPhases = arr(hybrid.phases).filter((p) => str(p.id));
|
|
95
|
+
const features = arr(pg.features);
|
|
96
|
+
const conceptRecs = arr(kg.concepts);
|
|
97
|
+
const conceptById = new Map(conceptRecs.map((c) => [str(c.id), c]));
|
|
98
|
+
if (rawPhases.length === 0) {
|
|
99
|
+
for (const c of conceptRecs) {
|
|
100
|
+
const node = feedNodeFromConcept(c);
|
|
101
|
+
nodes.push(node);
|
|
102
|
+
for (const p of strs(c.prerequisites)) edges.push({ from: p, to: node.id, kind: "knowledge", reason: "concept prerequisite" });
|
|
103
|
+
}
|
|
104
|
+
features.forEach((f, fi) => addFeatureGroup(f, str(f.id, "F" + pad2(fi + 1)), nodes, edges, groupings));
|
|
105
|
+
linkHybridKnowledgeEdges(hybrid, nodes, edges);
|
|
106
|
+
newKeywordsInOrder(nodes);
|
|
107
|
+
return validateCurriculumFeed({
|
|
108
|
+
schema_version: 2,
|
|
109
|
+
source: { graph_type: "hybrid_graph", warnings: opts.warnings ?? [], hallucination_count: opts.hallucinationCount ?? 0 },
|
|
110
|
+
learning_nodes: nodes,
|
|
111
|
+
dependency_edges: edges,
|
|
112
|
+
suggested_groupings: groupings,
|
|
113
|
+
phases: []
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
const featureOrder = /* @__PURE__ */ new Map();
|
|
117
|
+
features.forEach((f, fi) => featureOrder.set(str(f.id, "F" + pad2(fi + 1)), fi));
|
|
118
|
+
const stepIdsByFeature = /* @__PURE__ */ new Map();
|
|
119
|
+
for (const f of features) {
|
|
120
|
+
const fid = str(f.id);
|
|
121
|
+
stepIdsByFeature.set(fid, arr(f.steps).map((s) => str(s.id)).filter(Boolean));
|
|
122
|
+
}
|
|
123
|
+
const emitCtx = [];
|
|
124
|
+
const emittedConcepts = /* @__PURE__ */ new Set();
|
|
125
|
+
const linkList = arr(hybrid.links);
|
|
126
|
+
for (const rp of rawPhases) {
|
|
127
|
+
const pid = str(rp.id);
|
|
128
|
+
const order = num(rp.order, emitCtx.length + 1);
|
|
129
|
+
const conceptOrder = [];
|
|
130
|
+
const intros = arr(rp.introduces);
|
|
131
|
+
const news = intros.filter((i) => str(i.aspect, "new") === "new");
|
|
132
|
+
const advanced = intros.filter((i) => str(i.aspect) === "advanced");
|
|
133
|
+
for (const intro of [...news, ...advanced]) {
|
|
134
|
+
const cid = str(intro.concept_id);
|
|
135
|
+
const c = conceptById.get(cid);
|
|
136
|
+
if (!c) {
|
|
137
|
+
throw new Error("Phase " + pid + " introduces unknown concept " + cid + " - fail-closed");
|
|
138
|
+
}
|
|
139
|
+
if (str(intro.aspect, "new") === "advanced" && !emittedConcepts.has(cid)) {
|
|
140
|
+
throw new Error("Phase " + pid + " advanced-revisits concept " + cid + " that was never emitted - fail-closed");
|
|
141
|
+
}
|
|
142
|
+
if (!emittedConcepts.has(cid)) {
|
|
143
|
+
const node = feedNodeFromConcept(c);
|
|
144
|
+
node.phase_id = pid;
|
|
145
|
+
node.introduce_aspect = "new";
|
|
146
|
+
nodes.push(node);
|
|
147
|
+
for (const p of strs(c.prerequisites)) {
|
|
148
|
+
if (!emittedConcepts.has(p)) {
|
|
149
|
+
throw new Error("Concept " + cid + " (phase " + pid + ") has prerequisite " + p + " that is not introduced in an earlier phase - fail-closed");
|
|
150
|
+
}
|
|
151
|
+
edges.push({ from: p, to: cid, kind: "knowledge", reason: "concept prerequisite" });
|
|
152
|
+
}
|
|
153
|
+
emittedConcepts.add(cid);
|
|
154
|
+
conceptOrder.push(cid);
|
|
155
|
+
} else {
|
|
156
|
+
const revisitId = cid + "__ADV" + pad2(order);
|
|
157
|
+
const node = feedNodeFromConcept(c);
|
|
158
|
+
node.id = revisitId;
|
|
159
|
+
node.name = str(c.name, cid) + " (advanced)";
|
|
160
|
+
node.phase_id = pid;
|
|
161
|
+
node.introduce_aspect = "advanced";
|
|
162
|
+
node.estimated_minutes = Math.max(15, Math.round(node.estimated_minutes * 0.5));
|
|
163
|
+
node.concept_codes = [str(c.code) || cid];
|
|
164
|
+
edges.push({ from: cid, to: revisitId, kind: "knowledge", reason: "advanced revisit of " + cid + " at phase " + pid });
|
|
165
|
+
nodes.push(node);
|
|
166
|
+
conceptOrder.push(revisitId);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const featureIds = strs(rp.feature_ids).filter((fid) => featureOrder.has(fid)).sort((a, b) => featureOrder.get(a) - featureOrder.get(b));
|
|
170
|
+
emitCtx.push({ id: pid, order, name: str(rp.name, pid), productCompletion: str(rp.product_completion), conceptOrder, featureIds });
|
|
171
|
+
}
|
|
172
|
+
for (const l of linkList) {
|
|
173
|
+
const cid = str(l.concept_id);
|
|
174
|
+
if (cid && conceptById.has(cid) && !emittedConcepts.has(cid)) {
|
|
175
|
+
throw new Error("Concept " + cid + " is consumed by links but never introduced in any phase - fail-closed");
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const seenFeatures = new Set(emitCtx.flatMap((e) => e.featureIds));
|
|
179
|
+
const missingFeatures = features.map((f) => str(f.id)).filter((fid) => fid && !seenFeatures.has(fid));
|
|
180
|
+
if (missingFeatures.length > 0) {
|
|
181
|
+
throw new Error("Features not covered by any phase: " + missingFeatures.join(", ") + " - fail-closed");
|
|
182
|
+
}
|
|
183
|
+
const phaseNodeIds = [];
|
|
184
|
+
for (const ctx of emitCtx) {
|
|
185
|
+
const ids = [...ctx.conceptOrder];
|
|
186
|
+
for (const fid of ctx.featureIds) {
|
|
187
|
+
const f = features.find((x) => str(x.id) === fid);
|
|
188
|
+
const stepIds = addFeatureGroup(f, fid, nodes, edges, groupings);
|
|
189
|
+
for (const sid of stepIds) {
|
|
190
|
+
const node = nodes.find((n) => n.id === sid);
|
|
191
|
+
node.phase_id = ctx.id;
|
|
192
|
+
}
|
|
193
|
+
ids.push(...stepIds);
|
|
194
|
+
}
|
|
195
|
+
phaseNodeIds.push(ids);
|
|
196
|
+
}
|
|
197
|
+
const edgeSeen = new Set(edges.map((e) => e.from + ">" + e.to));
|
|
198
|
+
const pushEdge = (from, to, reason) => {
|
|
199
|
+
const key = from + ">" + to;
|
|
200
|
+
if (edgeSeen.has(key)) return;
|
|
201
|
+
edgeSeen.add(key);
|
|
202
|
+
edges.push({ from, to, kind: "knowledge", reason });
|
|
203
|
+
};
|
|
204
|
+
for (const l of linkList) {
|
|
205
|
+
const cid = str(l.concept_id);
|
|
206
|
+
const fid = str(l.feature_id);
|
|
207
|
+
if (!conceptById.has(cid)) continue;
|
|
208
|
+
const firstStep = (stepIdsByFeature.get(fid) || [])[0];
|
|
209
|
+
if (!firstStep) continue;
|
|
210
|
+
const depth = str(l.depth, "cio");
|
|
211
|
+
pushEdge(cid, firstStep, str(l.relationship, "hybrid link") + " [" + depth + "]");
|
|
212
|
+
for (const n of nodes) {
|
|
213
|
+
if (n.kind === "concept" && n.concept_ref === cid && n.introduce_aspect === "advanced" && n.phase_id === str(l.phase_id)) {
|
|
214
|
+
pushEdge(n.id, firstStep, "advanced application [" + depth + "]");
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
newKeywordsInOrder(nodes);
|
|
219
|
+
const feedOrder = new Map(nodes.map((n, i) => [n.id, i]));
|
|
220
|
+
const introducer = /* @__PURE__ */ new Map();
|
|
221
|
+
for (const n of nodes) for (const k of n.keywords?.new ?? []) {
|
|
222
|
+
const key = k.toLowerCase();
|
|
223
|
+
if (!introducer.has(key)) introducer.set(key, n.id);
|
|
224
|
+
}
|
|
225
|
+
for (const n of nodes) {
|
|
226
|
+
for (const k of n.keywords?.prerequisite ?? []) {
|
|
227
|
+
const src = introducer.get(k.toLowerCase());
|
|
228
|
+
if (!src || src === n.id) continue;
|
|
229
|
+
if (feedOrder.get(src) < feedOrder.get(n.id)) pushEdge(src, n.id, "keyword prerequisite [" + k + "]");
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
const phases = emitCtx.map((ctx, i) => ({
|
|
233
|
+
id: ctx.id,
|
|
234
|
+
order: ctx.order,
|
|
235
|
+
name: ctx.name,
|
|
236
|
+
product_completion: ctx.productCompletion || "Phase " + (i + 1) + " deliverable",
|
|
237
|
+
node_ids: phaseNodeIds[i]
|
|
238
|
+
}));
|
|
239
|
+
return validateCurriculumFeed({
|
|
240
|
+
schema_version: 2,
|
|
241
|
+
source: { graph_type: "hybrid_graph", warnings: opts.warnings ?? [], hallucination_count: opts.hallucinationCount ?? 0 },
|
|
242
|
+
learning_nodes: nodes,
|
|
243
|
+
dependency_edges: edges,
|
|
244
|
+
suggested_groupings: groupings,
|
|
245
|
+
phases
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
function linkHybridKnowledgeEdges(hybrid, nodes, edges, setStepHints) {
|
|
249
|
+
const conceptIds = new Set(nodes.filter((n) => n.kind === "concept").map((n) => n.concept_ref || n.id));
|
|
250
|
+
for (const l of arr(hybrid.links)) {
|
|
251
|
+
const conceptId = str(l.concept_id);
|
|
252
|
+
const featureId = str(l.feature_id);
|
|
253
|
+
if (!conceptIds.has(conceptId)) continue;
|
|
254
|
+
const depth = str(l.depth, "cio");
|
|
255
|
+
const firstStep = nodes.find((n) => n.feature_id === featureId);
|
|
256
|
+
if (firstStep) {
|
|
257
|
+
edges.push({ from: conceptId, to: firstStep.id, kind: "knowledge", reason: str(l.relationship, "hybrid link") + " [" + depth + "]" });
|
|
258
|
+
if (!firstStep.depth_hint) {
|
|
259
|
+
firstStep.depth_hint = ["ulo", "cio", "sio"].includes(depth) ? depth : "cio";
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function emitFromKnowledge(kg, opts) {
|
|
265
|
+
const nodes = [];
|
|
266
|
+
const edges = [];
|
|
267
|
+
const groupings = [];
|
|
268
|
+
for (const c of arr(kg.concepts)) {
|
|
269
|
+
const node = feedNodeFromConcept(c);
|
|
270
|
+
nodes.push(node);
|
|
271
|
+
for (const p of strs(c.prerequisites)) edges.push({ from: p, to: node.id, kind: "knowledge", reason: "concept prerequisite" });
|
|
272
|
+
}
|
|
273
|
+
let ci = 0;
|
|
274
|
+
for (const cat of arr(kg.categories)) {
|
|
275
|
+
ci += 1;
|
|
276
|
+
const ids = strs(cat.concept_ids).filter((id) => nodes.some((n) => n.id === id));
|
|
277
|
+
if (ids.length > 0) groupings.push({ id: str(cat.id, "cat" + ci), name: str(cat.name, "Category " + ci), node_ids: ids, rationale: str(cat.description, "") });
|
|
278
|
+
}
|
|
279
|
+
newKeywordsInOrder(nodes);
|
|
280
|
+
return validateCurriculumFeed({
|
|
281
|
+
schema_version: 2,
|
|
282
|
+
source: { graph_type: "knowledge_graph", warnings: opts.warnings ?? [], hallucination_count: opts.hallucinationCount ?? 0 },
|
|
283
|
+
learning_nodes: nodes,
|
|
284
|
+
dependency_edges: edges,
|
|
285
|
+
suggested_groupings: groupings,
|
|
286
|
+
phases: []
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
function emitFromProject(pg, opts) {
|
|
290
|
+
const nodes = [];
|
|
291
|
+
const edges = [];
|
|
292
|
+
const groupings = [];
|
|
293
|
+
arr(pg.features).forEach((f, fi) => addFeatureGroup(f, str(f.id, "F" + pad2(fi + 1)), nodes, edges, groupings));
|
|
294
|
+
if (nodes.length === 0 && arr(pg.features).length > 0) {
|
|
295
|
+
const hasTasks = arr(pg.implementation ?? {}).length > 0 || arr(pg.implementation?.tasks).length > 0;
|
|
296
|
+
throw new Error(
|
|
297
|
+
"Project graph has " + arr(pg.features).length + " features but NO features[].steps - the curriculum feed cannot emit planning nodes." + (hasTasks ? " This looks like a legacy knowledge-tree graph (implementation.tasks). Regenerate it with the domain-kit v3 pipeline (runProjectGraphPipeline / hybridGraphPipeline), then re-run." : " Every feature must carry at least one step with id/name/keywords/effort for curriculum planning.")
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
newKeywordsInOrder(nodes);
|
|
301
|
+
return validateCurriculumFeed({
|
|
302
|
+
schema_version: 2,
|
|
303
|
+
source: { graph_type: "project_graph", warnings: opts.warnings ?? [], hallucination_count: opts.hallucinationCount ?? 0 },
|
|
304
|
+
learning_nodes: nodes,
|
|
305
|
+
dependency_edges: edges,
|
|
306
|
+
suggested_groupings: groupings,
|
|
307
|
+
phases: []
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
function emitCurriculumFeed(graph, opts = {}) {
|
|
311
|
+
const g = graph;
|
|
312
|
+
if (arr(g.learning_nodes).length > 0) {
|
|
313
|
+
return validateCurriculumFeed(g);
|
|
314
|
+
}
|
|
315
|
+
if (g.project_graph || g.knowledge_graph || arr(g.links).length > 0 || arr(g.phases).length > 0) {
|
|
316
|
+
return emitFromHybrid(g, opts);
|
|
317
|
+
}
|
|
318
|
+
if (arr(g.concepts).length > 0 || str(g.type) === "knowledge_graph") return emitFromKnowledge(g, opts);
|
|
319
|
+
return emitFromProject(g, opts);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export { emitCurriculumFeed };
|
|
323
|
+
//# sourceMappingURL=chunk-3H2HX7PR.mjs.map
|
|
324
|
+
//# sourceMappingURL=chunk-3H2HX7PR.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/feed/curriculumFeedEmitter.ts"],"names":[],"mappings":"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","file":"chunk-3H2HX7PR.mjs","sourcesContent":["import {\n validateCurriculumFeed, type CurriculumFeed, type FeedNode, type FeedEdge, type FeedGrouping,\n} from '../schemas/curriculumFeedSchema';\n\n/**\n * CurriculumFeed emitter - pure transformation from a VERIFIED domain graph\n * to the planner-ready feed. No LLM calls: hints derive from existing\n * annotations only. Validates its own output (fail-closed).\n *\n * Phase-driven emission (hybrid graphs with development phases): the teaching\n * sequence IS the phase sequence. Concepts are emitted at the phase that\n * introduces them (aspect new) or revisits them at higher depth (aspect\n * advanced) - never as a trailing knowledge appendix. Concept minutes stay\n * UNMERGED: no prerequisite-minute inflation (the planner schedules the\n * revisit, not the total).\n */\n\nexport interface EmitFeedOptions {\n hallucinationCount?: number;\n warnings?: string[];\n}\n\nconst BLOOM_RANK: Record<string, number> = { Remember: 0, Understand: 1, Apply: 2, Analyze: 3, Evaluate: 4, Create: 5 };\nconst pad2 = (n: number): string => String(n).padStart(2, '0');\n\ntype Rec = Record<string, unknown>;\nconst arr = (v: unknown): Rec[] => (Array.isArray(v) ? (v as Rec[]) : []);\nconst str = (v: unknown, d = ''): string => (typeof v === 'string' ? v : d);\nconst num = (v: unknown, d: number): number => (typeof v === 'number' && Number.isFinite(v) ? v : d);\nconst strs = (v: unknown): string[] => (Array.isArray(v) ? v.filter((x): x is string => typeof x === 'string') : []);\n\nfunction stepMinutes(step: Rec): number {\n const effort = (step.effort ?? {}) as Rec;\n return Math.max(0, Math.round(num(effort.estimated_minutes, num(step.estimated_minutes, 45))));\n}\n\nfunction newKeywordsInOrder(nodes: FeedNode[]): void {\n // First appearance in emission order becomes 'new'; everything seen before is 'prerequisite'.\n const seen = new Set<string>();\n for (const n of nodes) {\n const all = n.keywords.all;\n n.keywords.new = [];\n n.keywords.prerequisite = [];\n for (const k of all) {\n if (seen.has(k)) n.keywords.prerequisite.push(k);\n else n.keywords.new.push(k);\n seen.add(k);\n }\n }\n}\n\nfunction feedNodeFromStep(step: Rec, featureId: string): FeedNode {\n const id = str(step.id);\n return {\n id,\n kind: 'product_step',\n name: str(step.name, id),\n concept_codes: strs(step.concept_codes),\n keywords: { all: strs(step.keywords), new: [], prerequisite: [] },\n estimated_minutes: stepMinutes(step),\n bloom_hint: 'Apply',\n depth_hint: null,\n completion_level: 'standard',\n scaffolding: { scaffold_candidates: [], core_value_minutes: stepMinutes(step) },\n references: arr(step.files).map((f) => ({ file: str(f), evidence: 'step file' })).filter((f) => f.file),\n feature_id: featureId,\n concept_ref: null,\n phase_id: '',\n introduce_aspect: null,\n };\n}\n\nfunction feedNodeFromConcept(concept: Rec): FeedNode {\n const id = str(concept.id);\n const pts = arr(concept.problem_types)\n .map((p) => str(p.bloom_level, 'Understand'))\n .filter((b) => b in BLOOM_RANK)\n .sort((a, b) => BLOOM_RANK[a] - BLOOM_RANK[b]);\n const topBloom = pts[pts.length - 1] ?? 'Understand';\n return {\n id,\n kind: 'concept',\n name: str(concept.name, id),\n concept_codes: [str(concept.code) || id].filter(Boolean),\n keywords: { all: strs(concept.keywords), new: [], prerequisite: [] },\n estimated_minutes: Math.max(0, Math.round(num(concept.estimated_minutes, 30))),\n bloom_hint: topBloom as FeedNode['bloom_hint'],\n depth_hint: null,\n completion_level: 'standard',\n scaffolding: { scaffold_candidates: [], core_value_minutes: 0 },\n references: [],\n feature_id: null,\n concept_ref: id,\n phase_id: '',\n introduce_aspect: null,\n };\n}\n\nfunction addFeatureGroup(\n f: Rec, fid: string, nodes: FeedNode[], edges: FeedEdge[], groupings: FeedGrouping[],\n): string[] {\n const steps = arr(f.steps);\n const stepIds: string[] = [];\n let prev = '';\n for (let i = 0; i < steps.length; i++) {\n const stepNode = feedNodeFromStep(steps[i], fid);\n if (!stepNode.id) continue;\n stepIds.push(stepNode.id);\n nodes.push(stepNode);\n if (prev) edges.push({ from: prev, to: stepNode.id, kind: 'task', reason: 'feature build order' });\n prev = stepNode.id;\n }\n for (const dep of strs(f.depends_on)) {\n const depFirst = nodes.find((n) => n.feature_id === dep);\n if (depFirst && stepIds[0]) edges.push({ from: depFirst.id, to: stepIds[0], kind: 'task', reason: 'feature depends_on ' + dep });\n }\n if (stepIds.length > 0) groupings.push({ id: 'G-' + fid, name: str(f.name, fid), node_ids: stepIds, rationale: str(f.description, '') });\n return stepIds;\n}\n\ninterface EmitPhaseCtx {\n id: string;\n order: number;\n name: string;\n productCompletion: string;\n conceptOrder: string[]; // concept node ids in teaching order (new first, then advanced)\n featureIds: string[];\n}\n\n/**\n * Phase-driven hybrid emission:\n * - emit concepts per phase (aspect new first, then advanced) BEFORE the\n * phase's features; knowledge edges concept -> first step of consumer\n * features carry the JIT relation\n * - feature order inside a phase follows the project graph order\n * - phases and their node_ids go into feed.phases (teaching sequence)\n * - concept minutes stay UNMERGED (no prereq-minute accumulation)\n */\nfunction emitFromHybrid(hybrid: Rec, opts: EmitFeedOptions): CurriculumFeed {\n const nodes: FeedNode[] = [];\n const edges: FeedEdge[] = [];\n const groupings: FeedGrouping[] = [];\n const pg = (hybrid.project_graph ?? {}) as Rec;\n const kg = (hybrid.knowledge_graph ?? {}) as Rec;\n const rawPhases = arr(hybrid.phases).filter((p) => str(p.id));\n\n const features = arr(pg.features);\n const conceptRecs = arr(kg.concepts);\n const conceptById = new Map(conceptRecs.map((c) => [str(c.id), c]));\n\n // --- no phases -> legacy path (concepts first, then features) -----------\n if (rawPhases.length === 0) {\n for (const c of conceptRecs) {\n const node = feedNodeFromConcept(c);\n nodes.push(node);\n for (const p of strs(c.prerequisites)) edges.push({ from: p, to: node.id, kind: 'knowledge', reason: 'concept prerequisite' });\n }\n features.forEach((f, fi) => addFeatureGroup(f, str(f.id, 'F' + pad2(fi + 1)), nodes, edges, groupings));\n linkHybridKnowledgeEdges(hybrid, nodes, edges, true);\n newKeywordsInOrder(nodes);\n return validateCurriculumFeed({\n schema_version: 2,\n source: { graph_type: 'hybrid_graph', warnings: opts.warnings ?? [], hallucination_count: opts.hallucinationCount ?? 0 },\n learning_nodes: nodes,\n dependency_edges: edges,\n suggested_groupings: groupings,\n phases: [],\n });\n }\n\n // --- phase-driven emission ----------------------------------------------\n const featureOrder = new Map<string, number>();\n features.forEach((f, fi) => featureOrder.set(str(f.id, 'F' + pad2(fi + 1)), fi));\n const stepIdsByFeature = new Map<string, string[]>();\n for (const f of features) {\n const fid = str(f.id);\n stepIdsByFeature.set(fid, arr(f.steps).map((s) => str(s.id)).filter(Boolean));\n }\n\n const emitCtx: EmitPhaseCtx[] = [];\n const emittedConcepts = new Set<string>();\n const linkList = arr(hybrid.links);\n\n for (const rp of rawPhases) {\n const pid = str(rp.id);\n const order = num(rp.order, emitCtx.length + 1);\n const conceptOrder: string[] = [];\n const intros = arr(rp.introduces);\n // new introductions first, then advanced revisits (teaching order)\n const news = intros.filter((i) => str(i.aspect, 'new') === 'new');\n const advanced = intros.filter((i) => str(i.aspect) === 'advanced');\n for (const intro of [...news, ...advanced]) {\n const cid = str(intro.concept_id);\n const c = conceptById.get(cid);\n if (!c) {\n throw new Error('Phase ' + pid + ' introduces unknown concept ' + cid + ' - fail-closed');\n }\n if (str(intro.aspect, 'new') === 'advanced' && !emittedConcepts.has(cid)) {\n throw new Error('Phase ' + pid + ' advanced-revisits concept ' + cid + ' that was never emitted - fail-closed');\n }\n if (!emittedConcepts.has(cid)) {\n const node = feedNodeFromConcept(c);\n node.phase_id = pid;\n node.introduce_aspect = 'new';\n nodes.push(node);\n for (const p of strs(c.prerequisites)) {\n if (!emittedConcepts.has(p)) {\n throw new Error('Concept ' + cid + ' (phase ' + pid + ') has prerequisite ' + p + ' that is not introduced in an earlier phase - fail-closed');\n }\n edges.push({ from: p, to: cid, kind: 'knowledge', reason: 'concept prerequisite' });\n }\n emittedConcepts.add(cid);\n conceptOrder.push(cid);\n } else {\n // advanced revisit: re-emit as a NEW node with a revisit id so the\n // planner can schedule the higher-depth pass without double-counting\n // the first introduction\n const revisitId = cid + '__ADV' + pad2(order);\n const node = feedNodeFromConcept(c);\n node.id = revisitId;\n node.name = str(c.name, cid) + ' (advanced)';\n node.phase_id = pid;\n node.introduce_aspect = 'advanced';\n node.estimated_minutes = Math.max(15, Math.round(node.estimated_minutes * 0.5));\n node.concept_codes = [str(c.code) || cid];\n edges.push({ from: cid, to: revisitId, kind: 'knowledge', reason: 'advanced revisit of ' + cid + ' at phase ' + pid });\n nodes.push(node);\n conceptOrder.push(revisitId);\n }\n }\n const featureIds = strs(rp.feature_ids)\n .filter((fid) => featureOrder.has(fid))\n .sort((a, b) => featureOrder.get(a)! - featureOrder.get(b)!);\n emitCtx.push({ id: pid, order, name: str(rp.name, pid), productCompletion: str(rp.product_completion), conceptOrder, featureIds });\n }\n\n // Fail-closed: every concept consumed by a link must be emitted somewhere\n for (const l of linkList) {\n const cid = str(l.concept_id);\n if (cid && conceptById.has(cid) && !emittedConcepts.has(cid)) {\n throw new Error('Concept ' + cid + ' is consumed by links but never introduced in any phase - fail-closed');\n }\n }\n // Fail-closed: every feature must sit in exactly one emitted phase\n const seenFeatures = new Set(emitCtx.flatMap((e) => e.featureIds));\n const missingFeatures = features.map((f) => str(f.id)).filter((fid) => fid && !seenFeatures.has(fid));\n if (missingFeatures.length > 0) {\n throw new Error('Features not covered by any phase: ' + missingFeatures.join(', ') + ' - fail-closed');\n }\n\n // Emit feature steps phase by phase (teaching sequence)\n const phaseNodeIds: string[][] = [];\n for (const ctx of emitCtx) {\n const ids: string[] = [...ctx.conceptOrder];\n for (const fid of ctx.featureIds) {\n const f = features.find((x) => str(x.id) === fid)!;\n const stepIds = addFeatureGroup(f, fid, nodes, edges, groupings);\n for (const sid of stepIds) {\n const node = nodes.find((n) => n.id === sid)!;\n node.phase_id = ctx.id;\n }\n ids.push(...stepIds);\n }\n phaseNodeIds.push(ids);\n }\n\n // Knowledge edges: concept -> first step of each consumer feature (JIT).\n // Deduped by (from,to): several links may target the same feature.\n const edgeSeen = new Set(edges.map((e) => e.from + '>' + e.to));\n const pushEdge = (from: string, to: string, reason: string) => {\n const key = from + '>' + to;\n if (edgeSeen.has(key)) return;\n edgeSeen.add(key);\n edges.push({ from, to, kind: 'knowledge', reason });\n };\n for (const l of linkList) {\n const cid = str(l.concept_id);\n const fid = str(l.feature_id);\n if (!conceptById.has(cid)) continue;\n const firstStep = (stepIdsByFeature.get(fid) || [])[0];\n if (!firstStep) continue;\n const depth = str(l.depth, 'cio');\n pushEdge(cid, firstStep, str(l.relationship, 'hybrid link') + ' [' + depth + ']');\n // Advanced revisits feed only features of the revisit's OWN phase: a PH2\n // revisit must never become a prerequisite of a PH1 feature (teaching\n // order is phase-ordered).\n for (const n of nodes) {\n if (n.kind === 'concept' && n.concept_ref === cid && n.introduce_aspect === 'advanced' && n.phase_id === str(l.phase_id)) {\n pushEdge(n.id, firstStep, 'advanced application [' + depth + ']');\n }\n }\n }\n\n newKeywordsInOrder(nodes);\n\n // Deterministic keyword-prerequisite materialization: when a node declares a\n // keyword as PREREQUISITE, some other node must INTRODUCE it (new) EARLIER in\n // feed order. Missing edge = planner can schedule the consumer first while the\n // audit flags ordering it cannot fix. Edge kind 'keyword' — the planner\n // honors it for topological order; 'mastered_before_course' entries are exempt\n // (entry-level keywords need no introducer).\n const feedOrder = new Map(nodes.map((n, i) => [n.id, i]));\n const introducer = new Map<string, string>();\n for (const n of nodes) for (const k of n.keywords?.new ?? []) {\n const key = k.toLowerCase();\n if (!introducer.has(key)) introducer.set(key, n.id);\n }\n for (const n of nodes) {\n for (const k of n.keywords?.prerequisite ?? []) {\n const src = introducer.get(k.toLowerCase());\n if (!src || src === n.id) continue;\n if (feedOrder.get(src)! < feedOrder.get(n.id)!) pushEdge(src, n.id, 'keyword prerequisite [' + k + ']');\n }\n }\n\n const phases = emitCtx.map((ctx, i) => ({\n id: ctx.id,\n order: ctx.order,\n name: ctx.name,\n product_completion: ctx.productCompletion || 'Phase ' + (i + 1) + ' deliverable',\n node_ids: phaseNodeIds[i],\n }));\n\n return validateCurriculumFeed({\n schema_version: 2,\n source: { graph_type: 'hybrid_graph', warnings: opts.warnings ?? [], hallucination_count: opts.hallucinationCount ?? 0 },\n learning_nodes: nodes,\n dependency_edges: edges,\n suggested_groupings: groupings,\n phases,\n });\n}\n\n/** Legacy helper: knowledge edges from hybrid links (used by the no-phase path). */\nfunction linkHybridKnowledgeEdges(hybrid: Rec, nodes: FeedNode[], edges: FeedEdge[], setStepHints: boolean): void {\n const conceptIds = new Set(nodes.filter((n) => n.kind === 'concept').map((n) => n.concept_ref || n.id));\n for (const l of arr(hybrid.links)) {\n const conceptId = str(l.concept_id);\n const featureId = str(l.feature_id);\n if (!conceptIds.has(conceptId)) continue;\n const depth = str(l.depth, 'cio');\n const firstStep = nodes.find((n) => n.feature_id === featureId);\n if (firstStep) {\n edges.push({ from: conceptId, to: firstStep.id, kind: 'knowledge', reason: str(l.relationship, 'hybrid link') + ' [' + depth + ']' });\n if (setStepHints && !firstStep.depth_hint) {\n firstStep.depth_hint = (['ulo', 'cio', 'sio'] as const).includes(depth as 'ulo') ? (depth as FeedNode['depth_hint']) : 'cio';\n }\n }\n }\n}\n\nfunction emitFromKnowledge(kg: Rec, opts: EmitFeedOptions): CurriculumFeed {\n const nodes: FeedNode[] = [];\n const edges: FeedEdge[] = [];\n const groupings: FeedGrouping[] = [];\n for (const c of arr(kg.concepts)) {\n const node = feedNodeFromConcept(c);\n nodes.push(node);\n for (const p of strs(c.prerequisites)) edges.push({ from: p, to: node.id, kind: 'knowledge', reason: 'concept prerequisite' });\n }\n let ci = 0;\n for (const cat of arr(kg.categories)) {\n ci += 1;\n const ids = strs(cat.concept_ids).filter((id) => nodes.some((n) => n.id === id));\n if (ids.length > 0) groupings.push({ id: str(cat.id, 'cat' + ci), name: str(cat.name, 'Category ' + ci), node_ids: ids, rationale: str(cat.description, '') });\n }\n newKeywordsInOrder(nodes);\n return validateCurriculumFeed({\n schema_version: 2,\n source: { graph_type: 'knowledge_graph', warnings: opts.warnings ?? [], hallucination_count: opts.hallucinationCount ?? 0 },\n learning_nodes: nodes,\n dependency_edges: edges,\n suggested_groupings: groupings,\n phases: [],\n });\n}\n\nfunction emitFromProject(pg: Rec, opts: EmitFeedOptions): CurriculumFeed {\n const nodes: FeedNode[] = [];\n const edges: FeedEdge[] = [];\n const groupings: FeedGrouping[] = [];\n arr(pg.features).forEach((f, fi) => addFeatureGroup(f, str(f.id, 'F' + pad2(fi + 1)), nodes, edges, groupings));\n if (nodes.length === 0 && arr(pg.features).length > 0) {\n const hasTasks = arr((pg.implementation ?? {}) as Rec).length > 0 || arr((pg.implementation as Rec | undefined)?.tasks).length > 0;\n throw new Error(\n 'Project graph has ' + arr(pg.features).length + ' features but NO features[].steps - the curriculum feed cannot emit planning nodes.' +\n (hasTasks\n ? ' This looks like a legacy knowledge-tree graph (implementation.tasks). Regenerate it with the domain-kit v3 pipeline (runProjectGraphPipeline / hybridGraphPipeline), then re-run.'\n : ' Every feature must carry at least one step with id/name/keywords/effort for curriculum planning.'),\n );\n }\n newKeywordsInOrder(nodes);\n return validateCurriculumFeed({\n schema_version: 2,\n source: { graph_type: 'project_graph', warnings: opts.warnings ?? [], hallucination_count: opts.hallucinationCount ?? 0 },\n learning_nodes: nodes,\n dependency_edges: edges,\n suggested_groupings: groupings,\n phases: [],\n });\n}\n\n/** Emit a CurriculumFeed from whichever graph shape is provided (already verified upstream). */\nexport function emitCurriculumFeed(\n graph: unknown,\n opts: EmitFeedOptions = {},\n): CurriculumFeed {\n const g = graph as Rec;\n if (arr(g.learning_nodes).length > 0) {\n return validateCurriculumFeed(g); // already a feed - pass-through validation\n }\n if (g.project_graph || g.knowledge_graph || arr(g.links).length > 0 || arr(g.phases).length > 0) {\n return emitFromHybrid(g, opts);\n }\n if (arr(g.concepts).length > 0 || str(g.type) === 'knowledge_graph') return emitFromKnowledge(g, opts);\n return emitFromProject(g, opts);\n}\n"]}
|