@thanh01.pmt/domain-kit 0.1.0 → 0.2.1
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/LICENSE +21 -0
- package/dist/{chunk-GEUDHZE7.mjs → chunk-23TQPTHG.mjs} +173 -4
- package/dist/chunk-23TQPTHG.mjs.map +1 -0
- package/dist/chunk-7XGWW6CB.mjs +3 -0
- package/dist/{chunk-F4RZNBOE.mjs.map → chunk-7XGWW6CB.mjs.map} +1 -1
- package/dist/{chunk-VFART56O.mjs → chunk-BL5NBKPH.mjs} +12 -8
- package/dist/chunk-BL5NBKPH.mjs.map +1 -0
- package/dist/{chunk-3VVPSRAM.mjs → chunk-CRN6D4HG.mjs} +1056 -92
- package/dist/chunk-CRN6D4HG.mjs.map +1 -0
- package/dist/{chunk-3H2HX7PR.mjs → chunk-HUDFN4IX.mjs} +58 -8
- package/dist/chunk-HUDFN4IX.mjs.map +1 -0
- package/dist/chunk-IJZUV7N3.mjs +3 -0
- package/dist/chunk-IJZUV7N3.mjs.map +1 -0
- package/dist/chunk-R57WTNXW.mjs +3 -0
- package/dist/chunk-R57WTNXW.mjs.map +1 -0
- package/dist/chunk-T4Y2DE2W.mjs +187 -0
- package/dist/chunk-T4Y2DE2W.mjs.map +1 -0
- package/dist/{chunk-PM42MMDJ.mjs → chunk-WHBAGJWN.mjs} +48 -3
- package/dist/chunk-WHBAGJWN.mjs.map +1 -0
- package/dist/chunk-ZKLT27T4.mjs +3 -0
- package/dist/chunk-ZKLT27T4.mjs.map +1 -0
- package/dist/concepts/index.d.cts +2 -2
- package/dist/concepts/index.d.ts +2 -2
- package/dist/cpp-CZUPqJt9.d.cts +98 -0
- package/dist/cpp-CZUPqJt9.d.ts +98 -0
- package/dist/curriculumFeedEmitter-E5DAJYUZ.mjs +4 -0
- package/dist/curriculumFeedEmitter-E5DAJYUZ.mjs.map +1 -0
- package/dist/detector/index.cjs +10 -6
- package/dist/detector/index.cjs.map +1 -1
- package/dist/detector/index.d.cts +4 -19
- package/dist/detector/index.d.ts +4 -19
- package/dist/detector/index.mjs +2 -1
- package/dist/domainProfileDetector-DHb1T0zB.d.ts +22 -0
- package/dist/domainProfileDetector-bzugdQmS.d.cts +22 -0
- package/dist/extractors/index.d.cts +3 -3
- package/dist/extractors/index.d.ts +3 -3
- package/dist/feed/index.cjs +56 -6
- package/dist/feed/index.cjs.map +1 -1
- package/dist/feed/index.mjs +2 -1
- package/dist/graph/index.cjs +370 -66
- package/dist/graph/index.cjs.map +1 -1
- package/dist/graph/index.d.cts +7 -67
- package/dist/graph/index.d.ts +7 -67
- package/dist/graph/index.mjs +3 -2
- package/dist/hybridGraphPipeline-DVZYLCAQ.d.ts +159 -0
- package/dist/hybridGraphPipeline-DmWRKYXf.d.cts +159 -0
- package/dist/index.cjs +1303 -700
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -7
- package/dist/index.d.ts +9 -7
- package/dist/index.mjs +10 -7
- package/dist/{keywordExtractor-zPAz2isq.d.cts → keywordExtractor-BxlGRMHC.d.cts} +1 -1
- package/dist/{keywordExtractor-CKaXqSku.d.ts → keywordExtractor-DU5XRN8-.d.ts} +1 -1
- package/dist/parsers/index.cjs +185 -0
- package/dist/parsers/index.cjs.map +1 -1
- package/dist/parsers/index.d.cts +2 -98
- package/dist/parsers/index.d.ts +2 -98
- package/dist/parsers/index.mjs +2 -1
- package/dist/pipeline/index.cjs +3142 -8
- package/dist/pipeline/index.cjs.map +1 -1
- package/dist/pipeline/index.d.cts +87 -4
- package/dist/pipeline/index.d.ts +87 -4
- package/dist/pipeline/index.mjs +6 -2
- package/dist/schemas/index.cjs +51 -0
- package/dist/schemas/index.cjs.map +1 -1
- package/dist/schemas/index.d.cts +222 -3
- package/dist/schemas/index.d.ts +222 -3
- package/dist/schemas/index.mjs +1 -1
- package/dist/syllabusParser-MSX1fEzW.d.cts +40 -0
- package/dist/syllabusParser-MSX1fEzW.d.ts +40 -0
- package/package.json +9 -10
- package/dist/chunk-3H2HX7PR.mjs.map +0 -1
- package/dist/chunk-3VVPSRAM.mjs.map +0 -1
- package/dist/chunk-F4RZNBOE.mjs +0 -3
- package/dist/chunk-GEUDHZE7.mjs.map +0 -1
- package/dist/chunk-MOKNFLUD.mjs +0 -851
- package/dist/chunk-MOKNFLUD.mjs.map +0 -1
- package/dist/chunk-PM42MMDJ.mjs.map +0 -1
- package/dist/chunk-VFART56O.mjs.map +0 -1
- package/dist/graphVerifier-DsTP9uAN.d.ts +0 -37
- package/dist/graphVerifier-QjgDAJce.d.cts +0 -37
package/dist/index.cjs
CHANGED
|
@@ -6,7 +6,492 @@ var os = require('os');
|
|
|
6
6
|
var path = require('path');
|
|
7
7
|
var child_process = require('child_process');
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
var __defProp = Object.defineProperty;
|
|
10
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
11
|
+
var __esm = (fn, res) => function __init() {
|
|
12
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
13
|
+
};
|
|
14
|
+
var __export = (target, all) => {
|
|
15
|
+
for (var name in all)
|
|
16
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
17
|
+
};
|
|
18
|
+
function validateCurriculumFeed(feed) {
|
|
19
|
+
const parsed = exports.CurriculumFeedSchema.parse(feed);
|
|
20
|
+
const ids = new Set(parsed.learning_nodes.map((n) => n.id));
|
|
21
|
+
if (ids.size !== parsed.learning_nodes.length) {
|
|
22
|
+
throw new Error("curriculum feed contains duplicate node ids");
|
|
23
|
+
}
|
|
24
|
+
for (const e of parsed.dependency_edges) {
|
|
25
|
+
if (!ids.has(e.from) || !ids.has(e.to)) {
|
|
26
|
+
throw new Error("curriculum feed edge " + e.from + " -> " + e.to + " references unknown node");
|
|
27
|
+
}
|
|
28
|
+
if (e.from === e.to) throw new Error("curriculum feed self-loop edge on " + e.from);
|
|
29
|
+
}
|
|
30
|
+
for (const g of parsed.suggested_groupings) {
|
|
31
|
+
for (const id of g.node_ids) {
|
|
32
|
+
if (!ids.has(id)) throw new Error("grouping " + g.id + " references unknown node " + id);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
for (const ph of parsed.phases) {
|
|
36
|
+
for (const id of ph.node_ids) {
|
|
37
|
+
if (!ids.has(id)) throw new Error("phase " + ph.id + " references unknown node " + id);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const phaseIds = parsed.phases.map((p) => p.id);
|
|
41
|
+
if (new Set(phaseIds).size !== phaseIds.length) throw new Error("curriculum feed contains duplicate phase ids");
|
|
42
|
+
return parsed;
|
|
43
|
+
}
|
|
44
|
+
exports.FeedKeywordsSchema = void 0; exports.FeedScaffoldCandidateSchema = void 0; exports.FeedNodeSchema = void 0; exports.FeedEdgeSchema = void 0; exports.FeedGroupingSchema = void 0; exports.FeedPhaseSchema = void 0; exports.CurriculumFeedSchema = void 0;
|
|
45
|
+
var init_curriculumFeedSchema = __esm({
|
|
46
|
+
"src/schemas/curriculumFeedSchema.ts"() {
|
|
47
|
+
exports.FeedKeywordsSchema = zod.z.object({
|
|
48
|
+
all: zod.z.array(zod.z.string()).default([]),
|
|
49
|
+
new: zod.z.array(zod.z.string()).default([]),
|
|
50
|
+
prerequisite: zod.z.array(zod.z.string()).default([])
|
|
51
|
+
});
|
|
52
|
+
exports.FeedScaffoldCandidateSchema = zod.z.object({
|
|
53
|
+
item: zod.z.string(),
|
|
54
|
+
reason: zod.z.string().default(""),
|
|
55
|
+
minutes_saved: zod.z.number().int().nonnegative().default(0),
|
|
56
|
+
suggest: zod.z.enum(["provide_full", "provide_partial", "provide_reference"]).default("provide_full")
|
|
57
|
+
});
|
|
58
|
+
exports.FeedNodeSchema = zod.z.object({
|
|
59
|
+
id: zod.z.string().min(1),
|
|
60
|
+
kind: zod.z.enum(["skill", "concept", "product_step"]),
|
|
61
|
+
name: zod.z.string().min(1),
|
|
62
|
+
concept_codes: zod.z.array(zod.z.string()).default([]),
|
|
63
|
+
keywords: exports.FeedKeywordsSchema,
|
|
64
|
+
estimated_minutes: zod.z.number().int().nonnegative(),
|
|
65
|
+
bloom_hint: zod.z.enum(["Remember", "Understand", "Apply", "Analyze", "Evaluate", "Create"]).nullable().default(null),
|
|
66
|
+
depth_hint: zod.z.enum(["ulo", "cio", "sio"]).nullable().default(null),
|
|
67
|
+
completion_level: zod.z.enum(["mvp", "standard", "stretch"]).default("standard"),
|
|
68
|
+
scaffolding: zod.z.object({
|
|
69
|
+
scaffold_candidates: zod.z.array(exports.FeedScaffoldCandidateSchema).default([]),
|
|
70
|
+
core_value_minutes: zod.z.number().int().nonnegative().default(0)
|
|
71
|
+
}).default({ scaffold_candidates: [], core_value_minutes: 0 }),
|
|
72
|
+
references: zod.z.array(zod.z.object({ file: zod.z.string().min(1), evidence: zod.z.string().default("") })).default([]),
|
|
73
|
+
feature_id: zod.z.string().nullable().default(null),
|
|
74
|
+
concept_ref: zod.z.string().nullable().default(null),
|
|
75
|
+
// Progressive-completion bookkeeping (feed v2, additive):
|
|
76
|
+
// phase_id = development phase that introduces/uses this node;
|
|
77
|
+
// introduce_aspect = how the phase teaches a concept node (first intro vs advanced revisit).
|
|
78
|
+
phase_id: zod.z.string().default(""),
|
|
79
|
+
introduce_aspect: zod.z.enum(["new", "advanced"]).nullable().default(null)
|
|
80
|
+
});
|
|
81
|
+
exports.FeedEdgeSchema = zod.z.object({
|
|
82
|
+
from: zod.z.string().min(1),
|
|
83
|
+
to: zod.z.string().min(1),
|
|
84
|
+
kind: zod.z.enum(["knowledge", "task"]),
|
|
85
|
+
reason: zod.z.string().default("")
|
|
86
|
+
});
|
|
87
|
+
exports.FeedGroupingSchema = zod.z.object({
|
|
88
|
+
id: zod.z.string().min(1),
|
|
89
|
+
name: zod.z.string().min(1),
|
|
90
|
+
node_ids: zod.z.array(zod.z.string()).min(1),
|
|
91
|
+
rationale: zod.z.string().default("")
|
|
92
|
+
});
|
|
93
|
+
exports.FeedPhaseSchema = zod.z.object({
|
|
94
|
+
id: zod.z.string().min(1),
|
|
95
|
+
order: zod.z.number().int().positive(),
|
|
96
|
+
name: zod.z.string().min(1),
|
|
97
|
+
product_completion: zod.z.string().min(1),
|
|
98
|
+
node_ids: zod.z.array(zod.z.string()).min(1)
|
|
99
|
+
});
|
|
100
|
+
exports.CurriculumFeedSchema = zod.z.object({
|
|
101
|
+
schema_version: zod.z.literal(2),
|
|
102
|
+
source: zod.z.object({
|
|
103
|
+
graph_type: zod.z.enum(["project_graph", "knowledge_graph", "hybrid_graph"]),
|
|
104
|
+
warnings: zod.z.array(zod.z.string()).default([]),
|
|
105
|
+
hallucination_count: zod.z.number().int().nonnegative().default(0)
|
|
106
|
+
}),
|
|
107
|
+
learning_nodes: zod.z.array(exports.FeedNodeSchema).min(1, "curriculum feed must contain at least one learning node"),
|
|
108
|
+
dependency_edges: zod.z.array(exports.FeedEdgeSchema).default([]),
|
|
109
|
+
suggested_groupings: zod.z.array(exports.FeedGroupingSchema).default([]),
|
|
110
|
+
// Progressive completion phases (cross-feature). When non-empty, these — not
|
|
111
|
+
// suggested_groupings — define the teaching sequence and unit structure.
|
|
112
|
+
phases: zod.z.array(exports.FeedPhaseSchema).default([])
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// src/feed/curriculumFeedEmitter.ts
|
|
118
|
+
var curriculumFeedEmitter_exports = {};
|
|
119
|
+
__export(curriculumFeedEmitter_exports, {
|
|
120
|
+
emitCurriculumFeed: () => emitCurriculumFeed
|
|
121
|
+
});
|
|
122
|
+
function stepMinutes(step) {
|
|
123
|
+
const effort = step.effort ?? {};
|
|
124
|
+
return Math.max(0, Math.round(num(effort.estimated_minutes, num(step.estimated_minutes, 45))));
|
|
125
|
+
}
|
|
126
|
+
function newKeywordsInOrder(nodes) {
|
|
127
|
+
const seen = /* @__PURE__ */ new Set();
|
|
128
|
+
for (const n of nodes) {
|
|
129
|
+
const all = n.keywords.all;
|
|
130
|
+
n.keywords.new = [];
|
|
131
|
+
n.keywords.prerequisite = [];
|
|
132
|
+
for (const k of all) {
|
|
133
|
+
if (seen.has(k)) n.keywords.prerequisite.push(k);
|
|
134
|
+
else n.keywords.new.push(k);
|
|
135
|
+
seen.add(k);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function feedNodeFromStep(step, featureId) {
|
|
140
|
+
const id = str(step.id);
|
|
141
|
+
return {
|
|
142
|
+
id,
|
|
143
|
+
kind: "product_step",
|
|
144
|
+
name: str(step.name, id),
|
|
145
|
+
concept_codes: strs(step.concept_codes),
|
|
146
|
+
keywords: { all: strs(step.keywords), new: [], prerequisite: [] },
|
|
147
|
+
estimated_minutes: stepMinutes(step),
|
|
148
|
+
bloom_hint: "Apply",
|
|
149
|
+
depth_hint: null,
|
|
150
|
+
completion_level: "standard",
|
|
151
|
+
scaffolding: { scaffold_candidates: [], core_value_minutes: stepMinutes(step) },
|
|
152
|
+
references: arr(step.files).map((f) => ({ file: str(f), evidence: "step file" })).filter((f) => f.file),
|
|
153
|
+
feature_id: featureId,
|
|
154
|
+
concept_ref: null,
|
|
155
|
+
phase_id: "",
|
|
156
|
+
introduce_aspect: null
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function deriveConceptDepthHint(concept) {
|
|
160
|
+
const explicit = str(concept.depth_hint);
|
|
161
|
+
if (explicit === "ulo" || explicit === "cio" || explicit === "sio") return explicit;
|
|
162
|
+
const sio = arr(concept.sio);
|
|
163
|
+
const prereqs = strs(concept.prerequisites);
|
|
164
|
+
const pts = arr(concept.problem_types);
|
|
165
|
+
const hasHighBloom = pts.some((p) => {
|
|
166
|
+
const b = str(p.bloom_level);
|
|
167
|
+
return b === "Apply" || b === "Analyze" || b === "Evaluate" || b === "Create";
|
|
168
|
+
});
|
|
169
|
+
if (sio.length >= 2 || hasHighBloom) return "sio";
|
|
170
|
+
if (str(concept.cio) || prereqs.length >= 1) return "cio";
|
|
171
|
+
return "ulo";
|
|
172
|
+
}
|
|
173
|
+
function feedNodeFromConcept(concept) {
|
|
174
|
+
const id = str(concept.id);
|
|
175
|
+
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]);
|
|
176
|
+
const topBloom = pts[pts.length - 1] ?? "Understand";
|
|
177
|
+
const rawRefs = arr(concept.references);
|
|
178
|
+
const references = rawRefs.length > 0 ? rawRefs.map((r) => ({ file: str(r.file, "syllabus.md"), evidence: str(r.evidence, str(concept.name)) })).filter((r) => r.file) : [{ file: "syllabus.md", evidence: str(concept.category ? `Topic under category ${str(concept.category)}` : str(concept.name), id) }];
|
|
179
|
+
const problemScaffolds = arr(concept.problem_types).map((pt) => ({
|
|
180
|
+
item: str(pt.name, "Problem type"),
|
|
181
|
+
reason: str(pt.description, "Formative practice check"),
|
|
182
|
+
minutes_saved: num(pt.estimated_minutes, 10),
|
|
183
|
+
suggest: "provide_partial"
|
|
184
|
+
}));
|
|
185
|
+
return {
|
|
186
|
+
id,
|
|
187
|
+
kind: "concept",
|
|
188
|
+
name: str(concept.name, id),
|
|
189
|
+
concept_codes: [str(concept.code) || id].filter(Boolean),
|
|
190
|
+
keywords: { all: strs(concept.keywords), new: [], prerequisite: [] },
|
|
191
|
+
estimated_minutes: Math.max(0, Math.round(num(concept.estimated_minutes, 15))),
|
|
192
|
+
bloom_hint: topBloom,
|
|
193
|
+
depth_hint: deriveConceptDepthHint(concept),
|
|
194
|
+
completion_level: "standard",
|
|
195
|
+
scaffolding: {
|
|
196
|
+
scaffold_candidates: problemScaffolds,
|
|
197
|
+
core_value_minutes: Math.max(0, Math.round(num(concept.estimated_minutes, 15)))
|
|
198
|
+
},
|
|
199
|
+
references,
|
|
200
|
+
feature_id: null,
|
|
201
|
+
concept_ref: id,
|
|
202
|
+
phase_id: "",
|
|
203
|
+
introduce_aspect: null
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function addFeatureGroup(f, fid, nodes, edges, groupings) {
|
|
207
|
+
const steps = arr(f.steps);
|
|
208
|
+
const stepIds = [];
|
|
209
|
+
let prev = "";
|
|
210
|
+
for (let i = 0; i < steps.length; i++) {
|
|
211
|
+
const stepNode = feedNodeFromStep(steps[i], fid);
|
|
212
|
+
if (!stepNode.id) continue;
|
|
213
|
+
stepIds.push(stepNode.id);
|
|
214
|
+
nodes.push(stepNode);
|
|
215
|
+
if (prev) edges.push({ from: prev, to: stepNode.id, kind: "task", reason: "feature build order" });
|
|
216
|
+
prev = stepNode.id;
|
|
217
|
+
}
|
|
218
|
+
for (const dep of strs(f.depends_on)) {
|
|
219
|
+
const depFirst = nodes.find((n) => n.feature_id === dep);
|
|
220
|
+
if (depFirst && stepIds[0]) edges.push({ from: depFirst.id, to: stepIds[0], kind: "task", reason: "feature depends_on " + dep });
|
|
221
|
+
}
|
|
222
|
+
if (stepIds.length > 0) groupings.push({ id: "G-" + fid, name: str(f.name, fid), node_ids: stepIds, rationale: str(f.description, "") });
|
|
223
|
+
return stepIds;
|
|
224
|
+
}
|
|
225
|
+
function emitFromHybrid(hybrid, opts) {
|
|
226
|
+
const nodes = [];
|
|
227
|
+
const edges = [];
|
|
228
|
+
const groupings = [];
|
|
229
|
+
const pg = hybrid.project_graph ?? {};
|
|
230
|
+
const kg = hybrid.knowledge_graph ?? {};
|
|
231
|
+
const rawPhases = arr(hybrid.phases).filter((p) => str(p.id));
|
|
232
|
+
const features = arr(pg.features);
|
|
233
|
+
const conceptRecs = arr(kg.concepts);
|
|
234
|
+
const conceptById = new Map(conceptRecs.map((c) => [str(c.id), c]));
|
|
235
|
+
if (rawPhases.length === 0) {
|
|
236
|
+
for (const c of conceptRecs) {
|
|
237
|
+
const node = feedNodeFromConcept(c);
|
|
238
|
+
nodes.push(node);
|
|
239
|
+
for (const p of strs(c.prerequisites)) edges.push({ from: p, to: node.id, kind: "knowledge", reason: "concept prerequisite" });
|
|
240
|
+
}
|
|
241
|
+
features.forEach((f, fi) => addFeatureGroup(f, str(f.id, "F" + pad22(fi + 1)), nodes, edges, groupings));
|
|
242
|
+
linkHybridKnowledgeEdges(hybrid, nodes, edges);
|
|
243
|
+
newKeywordsInOrder(nodes);
|
|
244
|
+
return validateCurriculumFeed({
|
|
245
|
+
schema_version: 2,
|
|
246
|
+
source: { graph_type: "hybrid_graph", warnings: opts.warnings ?? [], hallucination_count: opts.hallucinationCount ?? 0 },
|
|
247
|
+
learning_nodes: nodes,
|
|
248
|
+
dependency_edges: edges,
|
|
249
|
+
suggested_groupings: groupings,
|
|
250
|
+
phases: []
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
const featureOrder = /* @__PURE__ */ new Map();
|
|
254
|
+
features.forEach((f, fi) => featureOrder.set(str(f.id, "F" + pad22(fi + 1)), fi));
|
|
255
|
+
const stepIdsByFeature = /* @__PURE__ */ new Map();
|
|
256
|
+
for (const f of features) {
|
|
257
|
+
const fid = str(f.id);
|
|
258
|
+
stepIdsByFeature.set(fid, arr(f.steps).map((s) => str(s.id)).filter(Boolean));
|
|
259
|
+
}
|
|
260
|
+
const emitCtx = [];
|
|
261
|
+
const emittedConcepts = /* @__PURE__ */ new Set();
|
|
262
|
+
const linkList = arr(hybrid.links);
|
|
263
|
+
for (const rp of rawPhases) {
|
|
264
|
+
const pid = str(rp.id);
|
|
265
|
+
const order = num(rp.order, emitCtx.length + 1);
|
|
266
|
+
const conceptOrder = [];
|
|
267
|
+
const intros = arr(rp.introduces);
|
|
268
|
+
const news = intros.filter((i) => str(i.aspect, "new") === "new");
|
|
269
|
+
const advanced = intros.filter((i) => str(i.aspect) === "advanced");
|
|
270
|
+
for (const intro of [...news, ...advanced]) {
|
|
271
|
+
const cid = str(intro.concept_id);
|
|
272
|
+
const c = conceptById.get(cid);
|
|
273
|
+
if (!c) {
|
|
274
|
+
throw new Error("Phase " + pid + " introduces unknown concept " + cid + " - fail-closed");
|
|
275
|
+
}
|
|
276
|
+
if (str(intro.aspect, "new") === "advanced" && !emittedConcepts.has(cid)) {
|
|
277
|
+
throw new Error("Phase " + pid + " advanced-revisits concept " + cid + " that was never emitted - fail-closed");
|
|
278
|
+
}
|
|
279
|
+
if (!emittedConcepts.has(cid)) {
|
|
280
|
+
const node = feedNodeFromConcept(c);
|
|
281
|
+
node.phase_id = pid;
|
|
282
|
+
node.introduce_aspect = "new";
|
|
283
|
+
nodes.push(node);
|
|
284
|
+
for (const p of strs(c.prerequisites)) {
|
|
285
|
+
if (!emittedConcepts.has(p)) {
|
|
286
|
+
throw new Error("Concept " + cid + " (phase " + pid + ") has prerequisite " + p + " that is not introduced in an earlier phase - fail-closed");
|
|
287
|
+
}
|
|
288
|
+
edges.push({ from: p, to: cid, kind: "knowledge", reason: "concept prerequisite" });
|
|
289
|
+
}
|
|
290
|
+
emittedConcepts.add(cid);
|
|
291
|
+
conceptOrder.push(cid);
|
|
292
|
+
} else {
|
|
293
|
+
const revisitId = cid + "__ADV" + pad22(order);
|
|
294
|
+
const node = feedNodeFromConcept(c);
|
|
295
|
+
node.id = revisitId;
|
|
296
|
+
node.name = str(c.name, cid) + " (advanced)";
|
|
297
|
+
node.phase_id = pid;
|
|
298
|
+
node.introduce_aspect = "advanced";
|
|
299
|
+
node.estimated_minutes = Math.max(15, Math.round(node.estimated_minutes * 0.5));
|
|
300
|
+
node.concept_codes = [str(c.code) || cid];
|
|
301
|
+
edges.push({ from: cid, to: revisitId, kind: "knowledge", reason: "advanced revisit of " + cid + " at phase " + pid });
|
|
302
|
+
nodes.push(node);
|
|
303
|
+
conceptOrder.push(revisitId);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
const featureIds = strs(rp.feature_ids).filter((fid) => featureOrder.has(fid)).sort((a, b) => featureOrder.get(a) - featureOrder.get(b));
|
|
307
|
+
emitCtx.push({ id: pid, order, name: str(rp.name, pid), productCompletion: str(rp.product_completion), conceptOrder, featureIds });
|
|
308
|
+
}
|
|
309
|
+
for (const l of linkList) {
|
|
310
|
+
const cid = str(l.concept_id);
|
|
311
|
+
if (cid && conceptById.has(cid) && !emittedConcepts.has(cid)) {
|
|
312
|
+
throw new Error("Concept " + cid + " is consumed by links but never introduced in any phase - fail-closed");
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const seenFeatures = new Set(emitCtx.flatMap((e) => e.featureIds));
|
|
316
|
+
const missingFeatures = features.map((f) => str(f.id)).filter((fid) => fid && !seenFeatures.has(fid));
|
|
317
|
+
if (missingFeatures.length > 0) {
|
|
318
|
+
throw new Error("Features not covered by any phase: " + missingFeatures.join(", ") + " - fail-closed");
|
|
319
|
+
}
|
|
320
|
+
const phaseNodeIds = [];
|
|
321
|
+
for (const ctx of emitCtx) {
|
|
322
|
+
const ids = [...ctx.conceptOrder];
|
|
323
|
+
for (const fid of ctx.featureIds) {
|
|
324
|
+
const f = features.find((x) => str(x.id) === fid);
|
|
325
|
+
const stepIds = addFeatureGroup(f, fid, nodes, edges, groupings);
|
|
326
|
+
for (const sid of stepIds) {
|
|
327
|
+
const node = nodes.find((n) => n.id === sid);
|
|
328
|
+
node.phase_id = ctx.id;
|
|
329
|
+
}
|
|
330
|
+
ids.push(...stepIds);
|
|
331
|
+
}
|
|
332
|
+
phaseNodeIds.push(ids);
|
|
333
|
+
}
|
|
334
|
+
const edgeSeen = new Set(edges.map((e) => e.from + ">" + e.to));
|
|
335
|
+
const pushEdge = (from, to, reason) => {
|
|
336
|
+
const key = from + ">" + to;
|
|
337
|
+
if (edgeSeen.has(key)) return;
|
|
338
|
+
edgeSeen.add(key);
|
|
339
|
+
edges.push({ from, to, kind: "knowledge", reason });
|
|
340
|
+
};
|
|
341
|
+
for (const l of linkList) {
|
|
342
|
+
const cid = str(l.concept_id);
|
|
343
|
+
const fid = str(l.feature_id);
|
|
344
|
+
if (!conceptById.has(cid)) continue;
|
|
345
|
+
const firstStep = (stepIdsByFeature.get(fid) || [])[0];
|
|
346
|
+
if (!firstStep) continue;
|
|
347
|
+
const depth = str(l.depth, "cio");
|
|
348
|
+
pushEdge(cid, firstStep, str(l.relationship, "hybrid link") + " [" + depth + "]");
|
|
349
|
+
for (const n of nodes) {
|
|
350
|
+
if (n.kind === "concept" && n.concept_ref === cid && n.introduce_aspect === "advanced" && n.phase_id === str(l.phase_id)) {
|
|
351
|
+
pushEdge(n.id, firstStep, "advanced application [" + depth + "]");
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
newKeywordsInOrder(nodes);
|
|
356
|
+
const feedOrder = new Map(nodes.map((n, i) => [n.id, i]));
|
|
357
|
+
const introducer = /* @__PURE__ */ new Map();
|
|
358
|
+
for (const n of nodes) for (const k of n.keywords?.new ?? []) {
|
|
359
|
+
const key = k.toLowerCase();
|
|
360
|
+
if (!introducer.has(key)) introducer.set(key, n.id);
|
|
361
|
+
}
|
|
362
|
+
for (const n of nodes) {
|
|
363
|
+
for (const k of n.keywords?.prerequisite ?? []) {
|
|
364
|
+
const src = introducer.get(k.toLowerCase());
|
|
365
|
+
if (!src || src === n.id) continue;
|
|
366
|
+
if (feedOrder.get(src) < feedOrder.get(n.id)) pushEdge(src, n.id, "keyword prerequisite [" + k + "]");
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
const phases = emitCtx.map((ctx, i) => ({
|
|
370
|
+
id: ctx.id,
|
|
371
|
+
order: ctx.order,
|
|
372
|
+
name: ctx.name,
|
|
373
|
+
product_completion: ctx.productCompletion || "Phase " + (i + 1) + " deliverable",
|
|
374
|
+
node_ids: phaseNodeIds[i]
|
|
375
|
+
}));
|
|
376
|
+
return validateCurriculumFeed({
|
|
377
|
+
schema_version: 2,
|
|
378
|
+
source: { graph_type: "hybrid_graph", warnings: opts.warnings ?? [], hallucination_count: opts.hallucinationCount ?? 0 },
|
|
379
|
+
learning_nodes: nodes,
|
|
380
|
+
dependency_edges: edges,
|
|
381
|
+
suggested_groupings: groupings,
|
|
382
|
+
phases
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
function linkHybridKnowledgeEdges(hybrid, nodes, edges, setStepHints) {
|
|
386
|
+
const conceptIds = new Set(nodes.filter((n) => n.kind === "concept").map((n) => n.concept_ref || n.id));
|
|
387
|
+
for (const l of arr(hybrid.links)) {
|
|
388
|
+
const conceptId = str(l.concept_id);
|
|
389
|
+
const featureId = str(l.feature_id);
|
|
390
|
+
if (!conceptIds.has(conceptId)) continue;
|
|
391
|
+
const depth = str(l.depth, "cio");
|
|
392
|
+
const firstStep = nodes.find((n) => n.feature_id === featureId);
|
|
393
|
+
if (firstStep) {
|
|
394
|
+
edges.push({ from: conceptId, to: firstStep.id, kind: "knowledge", reason: str(l.relationship, "hybrid link") + " [" + depth + "]" });
|
|
395
|
+
if (!firstStep.depth_hint) {
|
|
396
|
+
firstStep.depth_hint = ["ulo", "cio", "sio"].includes(depth) ? depth : "cio";
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
function emitFromKnowledge(kg, opts) {
|
|
402
|
+
const nodes = [];
|
|
403
|
+
const edges = [];
|
|
404
|
+
const groupings = [];
|
|
405
|
+
const conceptIds = new Set(arr(kg.concepts).map((c) => str(c.id)));
|
|
406
|
+
for (const c of arr(kg.concepts)) {
|
|
407
|
+
const node = feedNodeFromConcept(c);
|
|
408
|
+
nodes.push(node);
|
|
409
|
+
for (const p of strs(c.prerequisites)) {
|
|
410
|
+
if (p !== node.id && conceptIds.has(p)) {
|
|
411
|
+
edges.push({
|
|
412
|
+
from: p,
|
|
413
|
+
to: node.id,
|
|
414
|
+
kind: "knowledge",
|
|
415
|
+
reason: `concept prerequisite: ${p} must be understood before ${node.id}`
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
if (arr(kg.learning_path).length > 0) {
|
|
421
|
+
const orderMap = /* @__PURE__ */ new Map();
|
|
422
|
+
arr(kg.learning_path).forEach((s, idx) => {
|
|
423
|
+
const cid = str(s.concept_id);
|
|
424
|
+
if (cid) orderMap.set(cid, num(s.order, idx + 1));
|
|
425
|
+
});
|
|
426
|
+
nodes.sort((a, b) => (orderMap.get(a.id) ?? 999) - (orderMap.get(b.id) ?? 999));
|
|
427
|
+
}
|
|
428
|
+
let ci = 0;
|
|
429
|
+
for (const cat of arr(kg.categories)) {
|
|
430
|
+
ci += 1;
|
|
431
|
+
const ids = strs(cat.concept_ids).filter((id) => nodes.some((n) => n.id === id));
|
|
432
|
+
if (ids.length > 0) {
|
|
433
|
+
groupings.push({
|
|
434
|
+
id: str(cat.id, "cat" + ci),
|
|
435
|
+
name: str(cat.name, "Category " + ci),
|
|
436
|
+
node_ids: ids,
|
|
437
|
+
rationale: str(cat.description, "")
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
newKeywordsInOrder(nodes);
|
|
442
|
+
return validateCurriculumFeed({
|
|
443
|
+
schema_version: 2,
|
|
444
|
+
source: { graph_type: "knowledge_graph", warnings: opts.warnings ?? [], hallucination_count: opts.hallucinationCount ?? 0 },
|
|
445
|
+
learning_nodes: nodes,
|
|
446
|
+
dependency_edges: edges,
|
|
447
|
+
suggested_groupings: groupings,
|
|
448
|
+
phases: []
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
function emitFromProject(pg, opts) {
|
|
452
|
+
const nodes = [];
|
|
453
|
+
const edges = [];
|
|
454
|
+
const groupings = [];
|
|
455
|
+
arr(pg.features).forEach((f, fi) => addFeatureGroup(f, str(f.id, "F" + pad22(fi + 1)), nodes, edges, groupings));
|
|
456
|
+
if (nodes.length === 0 && arr(pg.features).length > 0) {
|
|
457
|
+
const hasTasks = arr(pg.implementation ?? {}).length > 0 || arr(pg.implementation?.tasks).length > 0;
|
|
458
|
+
throw new Error(
|
|
459
|
+
"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.")
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
newKeywordsInOrder(nodes);
|
|
463
|
+
return validateCurriculumFeed({
|
|
464
|
+
schema_version: 2,
|
|
465
|
+
source: { graph_type: "project_graph", warnings: opts.warnings ?? [], hallucination_count: opts.hallucinationCount ?? 0 },
|
|
466
|
+
learning_nodes: nodes,
|
|
467
|
+
dependency_edges: edges,
|
|
468
|
+
suggested_groupings: groupings,
|
|
469
|
+
phases: []
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
function emitCurriculumFeed(graph, opts = {}) {
|
|
473
|
+
const g = graph;
|
|
474
|
+
if (arr(g.learning_nodes).length > 0) {
|
|
475
|
+
return validateCurriculumFeed(g);
|
|
476
|
+
}
|
|
477
|
+
if (g.project_graph || g.knowledge_graph || arr(g.links).length > 0 || arr(g.phases).length > 0) {
|
|
478
|
+
return emitFromHybrid(g, opts);
|
|
479
|
+
}
|
|
480
|
+
if (arr(g.concepts).length > 0 || str(g.type) === "knowledge_graph") return emitFromKnowledge(g, opts);
|
|
481
|
+
return emitFromProject(g, opts);
|
|
482
|
+
}
|
|
483
|
+
var BLOOM_RANK, pad22, arr, str, num, strs;
|
|
484
|
+
var init_curriculumFeedEmitter = __esm({
|
|
485
|
+
"src/feed/curriculumFeedEmitter.ts"() {
|
|
486
|
+
init_curriculumFeedSchema();
|
|
487
|
+
BLOOM_RANK = { Remember: 0, Understand: 1, Apply: 2, Analyze: 3, Evaluate: 4, Create: 5 };
|
|
488
|
+
pad22 = (n) => String(n).padStart(2, "0");
|
|
489
|
+
arr = (v) => Array.isArray(v) ? v : [];
|
|
490
|
+
str = (v, d = "") => typeof v === "string" ? v : d;
|
|
491
|
+
num = (v, d) => typeof v === "number" && Number.isFinite(v) ? v : d;
|
|
492
|
+
strs = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
|
|
493
|
+
}
|
|
494
|
+
});
|
|
10
495
|
var StepEffortSchema = zod.z.object({
|
|
11
496
|
estimated_minutes: zod.z.number().optional(),
|
|
12
497
|
complexity: zod.z.enum(["low", "medium", "high"]).optional(),
|
|
@@ -378,6 +863,51 @@ var ThreeDesignTechStackExtensionSchema = zod.z.object({
|
|
|
378
863
|
canvas_size: zod.z.string().optional(),
|
|
379
864
|
export_formats: zod.z.array(zod.z.string()).optional().default([]).describe("STL, OBJ, STEP")
|
|
380
865
|
});
|
|
866
|
+
var MathTheoremSchema = zod.z.object({
|
|
867
|
+
name: zod.z.string(),
|
|
868
|
+
statement: zod.z.string(),
|
|
869
|
+
proof_outline: zod.z.string().optional(),
|
|
870
|
+
converse_statement: zod.z.string().optional()
|
|
871
|
+
});
|
|
872
|
+
var MathConceptExtensionSchema = zod.z.object({
|
|
873
|
+
formulas: zod.z.array(zod.z.string()).optional().default([]).describe("Mathematical formulas, e.g. a^2 + b^2 = c^2"),
|
|
874
|
+
theorems: zod.z.array(MathTheoremSchema).optional().default([]),
|
|
875
|
+
constructions: zod.z.array(zod.z.string()).optional().default([]).describe("Compass and ruler constructions, auxiliary lines"),
|
|
876
|
+
visual_aids: zod.z.array(zod.z.string()).optional().default([]).describe("Geometric figures, function graphs"),
|
|
877
|
+
common_mistakes: zod.z.array(zod.z.string()).optional().default([]).describe("False intuitions or algebraic traps")
|
|
878
|
+
});
|
|
879
|
+
var ExperimentProcedureSchema = zod.z.object({
|
|
880
|
+
name: zod.z.string(),
|
|
881
|
+
apparatus: zod.z.array(zod.z.string()).optional().default([]),
|
|
882
|
+
steps: zod.z.array(zod.z.string()).optional().default([]),
|
|
883
|
+
safety_warnings: zod.z.array(zod.z.string()).optional().default([]),
|
|
884
|
+
expected_observation: zod.z.string().optional()
|
|
885
|
+
});
|
|
886
|
+
var ScienceConceptExtensionSchema = zod.z.object({
|
|
887
|
+
experiments: zod.z.array(ExperimentProcedureSchema).optional().default([]),
|
|
888
|
+
instruments: zod.z.array(zod.z.string()).optional().default([]).describe("Lab equipment: microscope, beaker, sensor"),
|
|
889
|
+
variables: zod.z.object({
|
|
890
|
+
independent: zod.z.string().optional(),
|
|
891
|
+
dependent: zod.z.string().optional(),
|
|
892
|
+
controlled: zod.z.array(zod.z.string()).optional().default([])
|
|
893
|
+
}).optional(),
|
|
894
|
+
hypotheses: zod.z.array(zod.z.string()).optional().default([]),
|
|
895
|
+
laws_and_theories: zod.z.array(zod.z.string()).optional().default([])
|
|
896
|
+
});
|
|
897
|
+
var GrammarRuleSchema = zod.z.object({
|
|
898
|
+
rule_name: zod.z.string(),
|
|
899
|
+
pattern: zod.z.string(),
|
|
900
|
+
example_correct: zod.z.string(),
|
|
901
|
+
example_incorrect: zod.z.string().optional(),
|
|
902
|
+
exceptions: zod.z.array(zod.z.string()).optional().default([])
|
|
903
|
+
});
|
|
904
|
+
var LanguageConceptExtensionSchema = zod.z.object({
|
|
905
|
+
reading_genres: zod.z.array(zod.z.string()).optional().default([]).describe("argumentative, narrative, expository"),
|
|
906
|
+
grammar_rules: zod.z.array(GrammarRuleSchema).optional().default([]),
|
|
907
|
+
writing_tasks: zod.z.array(zod.z.string()).optional().default([]),
|
|
908
|
+
lexicon_keywords: zod.z.array(zod.z.string()).optional().default([]),
|
|
909
|
+
rhetorical_devices: zod.z.array(zod.z.string()).optional().default([])
|
|
910
|
+
});
|
|
381
911
|
var ProjectConceptLinkSchema = zod.z.object({
|
|
382
912
|
feature_id: zod.z.string().describe("Feature ID from project_graph"),
|
|
383
913
|
concept_id: zod.z.string().describe("Concept ID from knowledge_graph"),
|
|
@@ -516,99 +1046,9 @@ var DomainProfileSchema = zod.z.object({
|
|
|
516
1046
|
repoDir: zod.z.string().optional().default(""),
|
|
517
1047
|
syllabusText: zod.z.string().optional().default("")
|
|
518
1048
|
});
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
prerequisite: zod.z.array(zod.z.string()).default([])
|
|
523
|
-
});
|
|
524
|
-
var FeedScaffoldCandidateSchema = zod.z.object({
|
|
525
|
-
item: zod.z.string(),
|
|
526
|
-
reason: zod.z.string().default(""),
|
|
527
|
-
minutes_saved: zod.z.number().int().nonnegative().default(0),
|
|
528
|
-
suggest: zod.z.enum(["provide_full", "provide_partial", "provide_reference"]).default("provide_full")
|
|
529
|
-
});
|
|
530
|
-
var FeedNodeSchema = zod.z.object({
|
|
531
|
-
id: zod.z.string().min(1),
|
|
532
|
-
kind: zod.z.enum(["skill", "concept", "product_step"]),
|
|
533
|
-
name: zod.z.string().min(1),
|
|
534
|
-
concept_codes: zod.z.array(zod.z.string()).default([]),
|
|
535
|
-
keywords: FeedKeywordsSchema,
|
|
536
|
-
estimated_minutes: zod.z.number().int().nonnegative(),
|
|
537
|
-
bloom_hint: zod.z.enum(["Remember", "Understand", "Apply", "Analyze", "Evaluate", "Create"]).nullable().default(null),
|
|
538
|
-
depth_hint: zod.z.enum(["ulo", "cio", "sio"]).nullable().default(null),
|
|
539
|
-
completion_level: zod.z.enum(["mvp", "standard", "stretch"]).default("standard"),
|
|
540
|
-
scaffolding: zod.z.object({
|
|
541
|
-
scaffold_candidates: zod.z.array(FeedScaffoldCandidateSchema).default([]),
|
|
542
|
-
core_value_minutes: zod.z.number().int().nonnegative().default(0)
|
|
543
|
-
}).default({ scaffold_candidates: [], core_value_minutes: 0 }),
|
|
544
|
-
references: zod.z.array(zod.z.object({ file: zod.z.string().min(1), evidence: zod.z.string().default("") })).default([]),
|
|
545
|
-
feature_id: zod.z.string().nullable().default(null),
|
|
546
|
-
concept_ref: zod.z.string().nullable().default(null),
|
|
547
|
-
// Progressive-completion bookkeeping (feed v2, additive):
|
|
548
|
-
// phase_id = development phase that introduces/uses this node;
|
|
549
|
-
// introduce_aspect = how the phase teaches a concept node (first intro vs advanced revisit).
|
|
550
|
-
phase_id: zod.z.string().default(""),
|
|
551
|
-
introduce_aspect: zod.z.enum(["new", "advanced"]).nullable().default(null)
|
|
552
|
-
});
|
|
553
|
-
var FeedEdgeSchema = zod.z.object({
|
|
554
|
-
from: zod.z.string().min(1),
|
|
555
|
-
to: zod.z.string().min(1),
|
|
556
|
-
kind: zod.z.enum(["knowledge", "task"]),
|
|
557
|
-
reason: zod.z.string().default("")
|
|
558
|
-
});
|
|
559
|
-
var FeedGroupingSchema = zod.z.object({
|
|
560
|
-
id: zod.z.string().min(1),
|
|
561
|
-
name: zod.z.string().min(1),
|
|
562
|
-
node_ids: zod.z.array(zod.z.string()).min(1),
|
|
563
|
-
rationale: zod.z.string().default("")
|
|
564
|
-
});
|
|
565
|
-
var FeedPhaseSchema = zod.z.object({
|
|
566
|
-
id: zod.z.string().min(1),
|
|
567
|
-
order: zod.z.number().int().positive(),
|
|
568
|
-
name: zod.z.string().min(1),
|
|
569
|
-
product_completion: zod.z.string().min(1),
|
|
570
|
-
node_ids: zod.z.array(zod.z.string()).min(1)
|
|
571
|
-
});
|
|
572
|
-
var CurriculumFeedSchema = zod.z.object({
|
|
573
|
-
schema_version: zod.z.literal(2),
|
|
574
|
-
source: zod.z.object({
|
|
575
|
-
graph_type: zod.z.enum(["project_graph", "knowledge_graph", "hybrid_graph"]),
|
|
576
|
-
warnings: zod.z.array(zod.z.string()).default([]),
|
|
577
|
-
hallucination_count: zod.z.number().int().nonnegative().default(0)
|
|
578
|
-
}),
|
|
579
|
-
learning_nodes: zod.z.array(FeedNodeSchema).min(1, "curriculum feed must contain at least one learning node"),
|
|
580
|
-
dependency_edges: zod.z.array(FeedEdgeSchema).default([]),
|
|
581
|
-
suggested_groupings: zod.z.array(FeedGroupingSchema).default([]),
|
|
582
|
-
// Progressive completion phases (cross-feature). When non-empty, these — not
|
|
583
|
-
// suggested_groupings — define the teaching sequence and unit structure.
|
|
584
|
-
phases: zod.z.array(FeedPhaseSchema).default([])
|
|
585
|
-
});
|
|
586
|
-
function validateCurriculumFeed(feed) {
|
|
587
|
-
const parsed = CurriculumFeedSchema.parse(feed);
|
|
588
|
-
const ids = new Set(parsed.learning_nodes.map((n) => n.id));
|
|
589
|
-
if (ids.size !== parsed.learning_nodes.length) {
|
|
590
|
-
throw new Error("curriculum feed contains duplicate node ids");
|
|
591
|
-
}
|
|
592
|
-
for (const e of parsed.dependency_edges) {
|
|
593
|
-
if (!ids.has(e.from) || !ids.has(e.to)) {
|
|
594
|
-
throw new Error("curriculum feed edge " + e.from + " -> " + e.to + " references unknown node");
|
|
595
|
-
}
|
|
596
|
-
if (e.from === e.to) throw new Error("curriculum feed self-loop edge on " + e.from);
|
|
597
|
-
}
|
|
598
|
-
for (const g of parsed.suggested_groupings) {
|
|
599
|
-
for (const id of g.node_ids) {
|
|
600
|
-
if (!ids.has(id)) throw new Error("grouping " + g.id + " references unknown node " + id);
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
for (const ph of parsed.phases) {
|
|
604
|
-
for (const id of ph.node_ids) {
|
|
605
|
-
if (!ids.has(id)) throw new Error("phase " + ph.id + " references unknown node " + id);
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
const phaseIds = parsed.phases.map((p) => p.id);
|
|
609
|
-
if (new Set(phaseIds).size !== phaseIds.length) throw new Error("curriculum feed contains duplicate phase ids");
|
|
610
|
-
return parsed;
|
|
611
|
-
}
|
|
1049
|
+
|
|
1050
|
+
// src/schemas/index.ts
|
|
1051
|
+
init_curriculumFeedSchema();
|
|
612
1052
|
|
|
613
1053
|
// src/parsers/swift.ts
|
|
614
1054
|
var IMPORT_RE = /^import\s+(\w+)/gm;
|
|
@@ -921,6 +1361,190 @@ function mergeCppResults(results) {
|
|
|
921
1361
|
return merged;
|
|
922
1362
|
}
|
|
923
1363
|
|
|
1364
|
+
// src/parsers/syllabusParser.ts
|
|
1365
|
+
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
1366
|
+
"and",
|
|
1367
|
+
"the",
|
|
1368
|
+
"for",
|
|
1369
|
+
"with",
|
|
1370
|
+
"from",
|
|
1371
|
+
"that",
|
|
1372
|
+
"this",
|
|
1373
|
+
"into",
|
|
1374
|
+
"when",
|
|
1375
|
+
"while",
|
|
1376
|
+
"using",
|
|
1377
|
+
"their",
|
|
1378
|
+
"your",
|
|
1379
|
+
"each",
|
|
1380
|
+
"how",
|
|
1381
|
+
"why",
|
|
1382
|
+
"what",
|
|
1383
|
+
"between",
|
|
1384
|
+
"through",
|
|
1385
|
+
"about",
|
|
1386
|
+
"trong",
|
|
1387
|
+
"nhung",
|
|
1388
|
+
"cac",
|
|
1389
|
+
"cua",
|
|
1390
|
+
"cho",
|
|
1391
|
+
"voi",
|
|
1392
|
+
"duoc",
|
|
1393
|
+
"khong",
|
|
1394
|
+
"theo",
|
|
1395
|
+
"tren",
|
|
1396
|
+
"duoi",
|
|
1397
|
+
"va",
|
|
1398
|
+
"hoac",
|
|
1399
|
+
"thi",
|
|
1400
|
+
"la",
|
|
1401
|
+
"nhu"
|
|
1402
|
+
]);
|
|
1403
|
+
function extractTokens(text) {
|
|
1404
|
+
const words = text.toLowerCase().replace(/[^a-z0-9à-ỹ\s-]/gi, " ").split(/\s+/).filter((w) => w.length >= 3 && !STOP_WORDS.has(w));
|
|
1405
|
+
return [...new Set(words)];
|
|
1406
|
+
}
|
|
1407
|
+
function extractGradeBand(text) {
|
|
1408
|
+
const gbMatch = text.match(/(?:grade\s*band|độ\s*tuổi|lớp)\s*[:=]?\s*(?:lớp\s*)?(\d{1,2})\s*(?:-|đến|to)\s*(?:lớp\s*)?(\d{1,2})/i);
|
|
1409
|
+
if (gbMatch && gbMatch[1] && gbMatch[2]) {
|
|
1410
|
+
const min = parseInt(gbMatch[1], 10);
|
|
1411
|
+
const max = parseInt(gbMatch[2], 10);
|
|
1412
|
+
return [Math.min(min, max), Math.max(min, max)];
|
|
1413
|
+
}
|
|
1414
|
+
return void 0;
|
|
1415
|
+
}
|
|
1416
|
+
function extractDomain(text) {
|
|
1417
|
+
const lower = text.toLowerCase();
|
|
1418
|
+
if (lower.includes("computer science") || lower.includes("l\u1EADp tr\xECnh") || lower.includes("algorithm") || lower.includes("software")) return "software";
|
|
1419
|
+
if (lower.includes("to\xE1n") || lower.includes("geometry") || lower.includes("algebra") || lower.includes("h\xECnh h\u1ECDc") || lower.includes("mathematics")) return "math";
|
|
1420
|
+
if (lower.includes("v\u1EADt l\xFD") || lower.includes("h\xF3a h\u1ECDc") || lower.includes("sinh h\u1ECDc") || lower.includes("physics") || lower.includes("chemistry") || lower.includes("biology") || lower.includes("science")) return "science";
|
|
1421
|
+
if (lower.includes("ti\u1EBFng") || lower.includes("ng\u1EEF v\u0103n") || lower.includes("grammar") || lower.includes("language") || lower.includes("writing") || lower.includes("literature")) return "language";
|
|
1422
|
+
return void 0;
|
|
1423
|
+
}
|
|
1424
|
+
function parseSyllabus(rawText) {
|
|
1425
|
+
const lines = rawText.split(/\r?\n/);
|
|
1426
|
+
let subjectTitle = "";
|
|
1427
|
+
let subjectDesc = "";
|
|
1428
|
+
const units = [];
|
|
1429
|
+
const allTopics = [];
|
|
1430
|
+
const globalKeywordsSet = /* @__PURE__ */ new Set();
|
|
1431
|
+
let currentUnit = null;
|
|
1432
|
+
let currentTopic = null;
|
|
1433
|
+
let unitIndex = 0;
|
|
1434
|
+
let topicIndex = 0;
|
|
1435
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1436
|
+
const line = lines[i].trim();
|
|
1437
|
+
const lineNum = i + 1;
|
|
1438
|
+
if (!line) continue;
|
|
1439
|
+
if (line.startsWith("# ") && !subjectTitle) {
|
|
1440
|
+
subjectTitle = line.replace(/^#\s+/, "").trim();
|
|
1441
|
+
continue;
|
|
1442
|
+
}
|
|
1443
|
+
if (!currentUnit) {
|
|
1444
|
+
if (line.toLowerCase().startsWith("m\xF4n h\u1ECDc:") || line.toLowerCase().startsWith("subject:")) {
|
|
1445
|
+
const val = line.split(":")[1]?.trim() || "";
|
|
1446
|
+
if (val && !subjectTitle) subjectTitle = val;
|
|
1447
|
+
}
|
|
1448
|
+
if (line.toLowerCase().startsWith("m\xF4 t\u1EA3:") || line.toLowerCase().startsWith("description:")) {
|
|
1449
|
+
subjectDesc = line.split(":")[1]?.trim() || "";
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
const isUnitHeading = /^##\s+(?:chương|module|unit|phần|chủ đề)\s*(\d+|[ivx]+)?[:.-]?\s*(.*)/i.test(line) || /^##\s+([^#].*)/.test(line);
|
|
1453
|
+
if (isUnitHeading) {
|
|
1454
|
+
if (currentTopic) {
|
|
1455
|
+
currentTopic.rawLineEnd = lineNum - 1;
|
|
1456
|
+
currentTopic = null;
|
|
1457
|
+
}
|
|
1458
|
+
unitIndex += 1;
|
|
1459
|
+
const titleMatch = line.replace(/^##\s+/, "").trim();
|
|
1460
|
+
currentUnit = {
|
|
1461
|
+
id: `U${String(unitIndex).padStart(2, "0")}`,
|
|
1462
|
+
title: titleMatch,
|
|
1463
|
+
order: unitIndex,
|
|
1464
|
+
topics: []
|
|
1465
|
+
};
|
|
1466
|
+
units.push(currentUnit);
|
|
1467
|
+
continue;
|
|
1468
|
+
}
|
|
1469
|
+
const isTopicItem = /^(?:-\s*\*\*(?:bài|topic|lesson|chủ đề)\s*(\d+(?:\.\d+)?|\w+)?[:.-]?\s*(.*?)\*\*)/i.exec(line) || /^###\s+(?:bài|topic|lesson)?\s*(\d+(?:\.\d+)?|\w+)?[:.-]?\s*(.*)/i.exec(line) || /^-\s+\*\*(.*?)\*\*/.exec(line) || /^(\d+)\.\s+(?:(?:\*\*)?(?:bài|topic|lesson)?\s*(\d+(?:\.\d+)?|\w+)?[:.-]?\s*(.*?)(?:\*\*)?)$/i.exec(line);
|
|
1470
|
+
if (isTopicItem) {
|
|
1471
|
+
if (currentTopic) {
|
|
1472
|
+
currentTopic.rawLineEnd = lineNum - 1;
|
|
1473
|
+
}
|
|
1474
|
+
if (!currentUnit) {
|
|
1475
|
+
unitIndex += 1;
|
|
1476
|
+
currentUnit = {
|
|
1477
|
+
id: `U${String(unitIndex).padStart(2, "0")}`,
|
|
1478
|
+
title: subjectTitle || "General",
|
|
1479
|
+
order: unitIndex,
|
|
1480
|
+
topics: []
|
|
1481
|
+
};
|
|
1482
|
+
units.push(currentUnit);
|
|
1483
|
+
}
|
|
1484
|
+
topicIndex += 1;
|
|
1485
|
+
const rawName = (isTopicItem[3] || isTopicItem[2] || isTopicItem[1] || `Topic ${topicIndex}`).trim();
|
|
1486
|
+
currentTopic = {
|
|
1487
|
+
id: `T${String(topicIndex).padStart(2, "0")}`,
|
|
1488
|
+
title: rawName,
|
|
1489
|
+
unitId: currentUnit.id,
|
|
1490
|
+
unitTitle: currentUnit.title,
|
|
1491
|
+
description: "",
|
|
1492
|
+
learningObjectives: [],
|
|
1493
|
+
keywords: [],
|
|
1494
|
+
rawLineStart: lineNum,
|
|
1495
|
+
rawLineEnd: lineNum
|
|
1496
|
+
};
|
|
1497
|
+
currentUnit.topics.push(currentTopic);
|
|
1498
|
+
allTopics.push(currentTopic);
|
|
1499
|
+
continue;
|
|
1500
|
+
}
|
|
1501
|
+
if (currentTopic) {
|
|
1502
|
+
currentTopic.rawLineEnd = lineNum;
|
|
1503
|
+
const stripped = line.replace(/^[-*•\s]+/, "").trim();
|
|
1504
|
+
if (stripped.toLowerCase().startsWith("m\u1EE5c ti\xEAu:") || stripped.toLowerCase().startsWith("objectives:")) {
|
|
1505
|
+
const content = stripped.replace(/^(?:mục tiêu|objectives):\s*/i, "").trim();
|
|
1506
|
+
if (content) currentTopic.learningObjectives.push(content);
|
|
1507
|
+
continue;
|
|
1508
|
+
}
|
|
1509
|
+
if (stripped.toLowerCase().startsWith("t\u1EEB kh\xF3a:") || stripped.toLowerCase().startsWith("keywords:")) {
|
|
1510
|
+
const rawKw = stripped.replace(/^(?:từ khóa|keywords):\s*/i, "").trim();
|
|
1511
|
+
const tokens2 = rawKw.split(/[,;]+/).map((k) => k.trim()).filter(Boolean);
|
|
1512
|
+
for (const tok of tokens2) {
|
|
1513
|
+
currentTopic.keywords.push(tok);
|
|
1514
|
+
globalKeywordsSet.add(tok);
|
|
1515
|
+
}
|
|
1516
|
+
continue;
|
|
1517
|
+
}
|
|
1518
|
+
if (currentTopic.description) {
|
|
1519
|
+
currentTopic.description += " " + stripped;
|
|
1520
|
+
} else {
|
|
1521
|
+
currentTopic.description = stripped;
|
|
1522
|
+
}
|
|
1523
|
+
for (const tok of extractTokens(stripped)) {
|
|
1524
|
+
if (tok.length >= 4) globalKeywordsSet.add(tok);
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
if (currentTopic) {
|
|
1529
|
+
currentTopic.rawLineEnd = lines.length;
|
|
1530
|
+
}
|
|
1531
|
+
if (!subjectTitle && units.length > 0) {
|
|
1532
|
+
subjectTitle = units[0]?.title || "Untitled Subject";
|
|
1533
|
+
}
|
|
1534
|
+
return {
|
|
1535
|
+
subject: {
|
|
1536
|
+
title: subjectTitle || "Untitled Subject",
|
|
1537
|
+
domain: extractDomain(rawText),
|
|
1538
|
+
gradeBand: extractGradeBand(rawText),
|
|
1539
|
+
description: subjectDesc
|
|
1540
|
+
},
|
|
1541
|
+
units,
|
|
1542
|
+
allTopics,
|
|
1543
|
+
extractedKeywords: [...globalKeywordsSet],
|
|
1544
|
+
rawText
|
|
1545
|
+
};
|
|
1546
|
+
}
|
|
1547
|
+
|
|
924
1548
|
// src/extractors/keywordExtractor.ts
|
|
925
1549
|
var STDLIB_MODULES = /* @__PURE__ */ new Set([
|
|
926
1550
|
// Python
|
|
@@ -2128,11 +2752,15 @@ function detectLearningMode(options) {
|
|
|
2128
2752
|
|
|
2129
2753
|
// src/detector/domainProfileDetector.ts
|
|
2130
2754
|
function detectDomainProfile(options) {
|
|
2131
|
-
const
|
|
2755
|
+
const effectiveSyllabus = options.syllabusText || options.parsedSyllabus?.rawText || "";
|
|
2756
|
+
const inputResult = detectInputType({
|
|
2757
|
+
...options,
|
|
2758
|
+
syllabusText: effectiveSyllabus
|
|
2759
|
+
});
|
|
2132
2760
|
const domainResult = detectDomain({
|
|
2133
2761
|
description: options.description,
|
|
2134
2762
|
goal: options.goal,
|
|
2135
|
-
syllabusText:
|
|
2763
|
+
syllabusText: effectiveSyllabus,
|
|
2136
2764
|
techStack: options.techStack,
|
|
2137
2765
|
fileExtensions: options.fileExtensions,
|
|
2138
2766
|
repoUrl: options.repoUrl
|
|
@@ -2143,12 +2771,12 @@ function detectDomainProfile(options) {
|
|
|
2143
2771
|
goal: options.goal,
|
|
2144
2772
|
description: options.description,
|
|
2145
2773
|
hasRepo: !!(options.repoUrl || options.repoDir),
|
|
2146
|
-
hasSyllabus: !!(
|
|
2774
|
+
hasSyllabus: !!(effectiveSyllabus || options.chapters && options.chapters.length > 0 || options.parsedSyllabus)
|
|
2147
2775
|
});
|
|
2148
2776
|
const graphType = determineGraphType(modeResult.learningMode);
|
|
2149
2777
|
return {
|
|
2150
2778
|
inputType: inputResult.inputType,
|
|
2151
|
-
domainCategory: domainResult.domainCategory,
|
|
2779
|
+
domainCategory: domainResult.domainCategory === "other" && options.parsedSyllabus?.subject.domain ? options.parsedSyllabus.subject.domain : domainResult.domainCategory,
|
|
2152
2780
|
learningMode: modeResult.learningMode,
|
|
2153
2781
|
graphType,
|
|
2154
2782
|
confidence: {
|
|
@@ -2158,13 +2786,13 @@ function detectDomainProfile(options) {
|
|
|
2158
2786
|
},
|
|
2159
2787
|
evidence: [...inputResult.evidence, ...domainResult.evidence, ...modeResult.evidence],
|
|
2160
2788
|
detectedTechStack: domainResult.detectedTechStack,
|
|
2161
|
-
detectedSubject: domainResult.detectedSubject,
|
|
2789
|
+
detectedSubject: options.parsedSyllabus?.subject.title || domainResult.detectedSubject,
|
|
2162
2790
|
detectedDomain: domainResult.domainCategory,
|
|
2163
2791
|
goal: options.goal || "",
|
|
2164
2792
|
description: options.description || "",
|
|
2165
2793
|
repoUrl: options.repoUrl || "",
|
|
2166
2794
|
repoDir: options.repoDir || "",
|
|
2167
|
-
syllabusText:
|
|
2795
|
+
syllabusText: effectiveSyllabus
|
|
2168
2796
|
};
|
|
2169
2797
|
}
|
|
2170
2798
|
function determineGraphType(learningMode) {
|
|
@@ -2201,8 +2829,8 @@ function resolveProviderChain(config) {
|
|
|
2201
2829
|
}
|
|
2202
2830
|
push("legacy", process.env.LLM_BASE_URL || process.env.OPENAI_BASE_URL || "", process.env.LLM_API_KEY || process.env.OPENAI_API_KEY || "", process.env.LLM_MODEL || process.env.LLM_TIER_FAST || "");
|
|
2203
2831
|
push("dashscope", process.env.DASHSCOPE_BASE_URL || process.env.ALIBABA_BASE_URL || "", process.env.DASHSCOPE_API_KEY || process.env.ALIBABA_API_KEY || "", process.env.DASHSCOPE_MODEL || process.env.DEFAULT_AI_MODEL || "");
|
|
2204
|
-
push("nvidia", "https://integrate.api.nvidia.com/v1", process.env.NVIDIA_API_KEY || "", process.env.NVIDIA_MODEL || "nvidia/nemotron-3-ultra-550b-a55b");
|
|
2205
|
-
push("openrouter", "https://openrouter.ai/api/v1", process.env.OPENROUTER_API_KEY || "", process.env.OPENROUTER_MODEL || "nvidia/nemotron-3-ultra-550b-a55b");
|
|
2832
|
+
push("nvidia", "https://integrate.api.nvidia.com/v1", process.env.NVIDIA_API_KEY || "", process.env.NVIDIA_MODEL || "nvidia/nemotron-3-ultra-550b-a55b:free");
|
|
2833
|
+
push("openrouter", "https://openrouter.ai/api/v1", process.env.OPENROUTER_API_KEY || "", process.env.OPENROUTER_MODEL || "nvidia/nemotron-3-ultra-550b-a55b:free");
|
|
2206
2834
|
if (process.env.OPENROUTER_MODEL) {
|
|
2207
2835
|
const or = chain.find((c) => c.label === "openrouter");
|
|
2208
2836
|
if (or) {
|
|
@@ -3144,6 +3772,161 @@ var ULO_CODE_RE = /^ULO-[A-Z][A-Z0-9_]*-\d{2}$/;
|
|
|
3144
3772
|
var CIO_CODE_RE = /^CIO-[A-Z][A-Z0-9_]*-\d{2}-[A-Z][A-Z0-9_]*$/;
|
|
3145
3773
|
var SIO_CODE_RE = /^SIO-[A-Z0-9]+-[A-Z][A-Z0-9_]*-\d{2}$/;
|
|
3146
3774
|
|
|
3775
|
+
// src/graph/knowledgeGraphVerifier.ts
|
|
3776
|
+
function detectPrerequisiteCycles(concepts) {
|
|
3777
|
+
const cycles = [];
|
|
3778
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3779
|
+
const inStack = /* @__PURE__ */ new Set();
|
|
3780
|
+
const path = [];
|
|
3781
|
+
const prereqMap = /* @__PURE__ */ new Map();
|
|
3782
|
+
for (const c of concepts) {
|
|
3783
|
+
prereqMap.set(c.id, c.prerequisites || []);
|
|
3784
|
+
}
|
|
3785
|
+
function dfs(conceptId) {
|
|
3786
|
+
if (inStack.has(conceptId)) {
|
|
3787
|
+
const cycleStart = path.indexOf(conceptId);
|
|
3788
|
+
if (cycleStart >= 0) {
|
|
3789
|
+
cycles.push([...path.slice(cycleStart), conceptId]);
|
|
3790
|
+
}
|
|
3791
|
+
return;
|
|
3792
|
+
}
|
|
3793
|
+
if (visited.has(conceptId)) return;
|
|
3794
|
+
visited.add(conceptId);
|
|
3795
|
+
inStack.add(conceptId);
|
|
3796
|
+
path.push(conceptId);
|
|
3797
|
+
for (const prereq of prereqMap.get(conceptId) || []) {
|
|
3798
|
+
dfs(prereq);
|
|
3799
|
+
}
|
|
3800
|
+
path.pop();
|
|
3801
|
+
inStack.delete(conceptId);
|
|
3802
|
+
}
|
|
3803
|
+
for (const c of concepts) {
|
|
3804
|
+
if (!visited.has(c.id)) {
|
|
3805
|
+
dfs(c.id);
|
|
3806
|
+
}
|
|
3807
|
+
}
|
|
3808
|
+
return cycles;
|
|
3809
|
+
}
|
|
3810
|
+
function breakPrerequisiteCycles(concepts, cycles) {
|
|
3811
|
+
const removed = [];
|
|
3812
|
+
const fixed = concepts.map((c) => ({ ...c, prerequisites: [...c.prerequisites || []] }));
|
|
3813
|
+
for (const cycle of cycles) {
|
|
3814
|
+
if (cycle.length >= 2) {
|
|
3815
|
+
const breaker = cycle[cycle.length - 2];
|
|
3816
|
+
const target = cycle[cycle.length - 1];
|
|
3817
|
+
for (const concept of fixed) {
|
|
3818
|
+
if (concept.id === breaker && target) {
|
|
3819
|
+
concept.prerequisites = concept.prerequisites.filter((p) => p !== target);
|
|
3820
|
+
removed.push({ from: breaker, to: target });
|
|
3821
|
+
}
|
|
3822
|
+
}
|
|
3823
|
+
}
|
|
3824
|
+
}
|
|
3825
|
+
return { fixed, removed };
|
|
3826
|
+
}
|
|
3827
|
+
function auditSyllabusCoverage(concepts, syllabus) {
|
|
3828
|
+
if (!syllabus || syllabus.allTopics.length === 0) {
|
|
3829
|
+
return { coverageRatio: 1, totalTopics: 0, coveredTopics: 0, uncovered: [] };
|
|
3830
|
+
}
|
|
3831
|
+
const topics = syllabus.allTopics;
|
|
3832
|
+
const coveredSet = /* @__PURE__ */ new Set();
|
|
3833
|
+
for (const topic of topics) {
|
|
3834
|
+
const topicKeywords = /* @__PURE__ */ new Set([
|
|
3835
|
+
...topic.title.toLowerCase().split(/\s+/),
|
|
3836
|
+
...(topic.keywords || []).map((k) => k.toLowerCase())
|
|
3837
|
+
]);
|
|
3838
|
+
for (const concept of concepts) {
|
|
3839
|
+
concept.name.toLowerCase();
|
|
3840
|
+
(concept.description || "").toLowerCase();
|
|
3841
|
+
(concept.keywords || []).map((k) => k.toLowerCase());
|
|
3842
|
+
if (concept.id.toLowerCase() === topic.id.toLowerCase()) {
|
|
3843
|
+
coveredSet.add(topic.id);
|
|
3844
|
+
break;
|
|
3845
|
+
}
|
|
3846
|
+
const refs = concept.references || [];
|
|
3847
|
+
const hasTopicRef = refs.some(
|
|
3848
|
+
(r) => r.id === topic.id || r.title && r.title.toLowerCase() === topic.title.toLowerCase()
|
|
3849
|
+
);
|
|
3850
|
+
if (hasTopicRef) {
|
|
3851
|
+
coveredSet.add(topic.id);
|
|
3852
|
+
break;
|
|
3853
|
+
}
|
|
3854
|
+
const conceptKeywords = (concept.keywords || []).map((k) => k.toLowerCase());
|
|
3855
|
+
const hasOverlap = conceptKeywords.some((k) => topicKeywords.has(k)) || concept.name.toLowerCase().includes(topic.title.toLowerCase()) || topic.title.toLowerCase().includes(concept.name.toLowerCase());
|
|
3856
|
+
if (hasOverlap) {
|
|
3857
|
+
coveredSet.add(topic.id);
|
|
3858
|
+
break;
|
|
3859
|
+
}
|
|
3860
|
+
}
|
|
3861
|
+
}
|
|
3862
|
+
const totalTopics = topics.length;
|
|
3863
|
+
const coveredTopics = coveredSet.size;
|
|
3864
|
+
const coverageRatio = totalTopics > 0 ? coveredTopics / totalTopics : 1;
|
|
3865
|
+
const uncovered = topics.filter((t) => !coveredSet.has(t.id)).map((t) => `${t.id}: ${t.title}`);
|
|
3866
|
+
return { coverageRatio, totalTopics, coveredTopics, uncovered };
|
|
3867
|
+
}
|
|
3868
|
+
function verifyKnowledgeGraph(kg, syllabusOrOptions) {
|
|
3869
|
+
const syllabus = syllabusOrOptions && "parsedSyllabus" in syllabusOrOptions ? syllabusOrOptions.parsedSyllabus : syllabusOrOptions;
|
|
3870
|
+
const warnings = [];
|
|
3871
|
+
let concepts = [...kg.concepts || []];
|
|
3872
|
+
const conceptIds = new Set(concepts.map((c) => c.id));
|
|
3873
|
+
for (const c of concepts) {
|
|
3874
|
+
if ((c.prerequisites || []).includes(c.id)) {
|
|
3875
|
+
c.prerequisites = (c.prerequisites || []).filter((p) => p !== c.id);
|
|
3876
|
+
warnings.push(`Self-loop removed on concept ${c.id}`);
|
|
3877
|
+
}
|
|
3878
|
+
}
|
|
3879
|
+
const danglingPrerequisites = [];
|
|
3880
|
+
for (const c of concepts) {
|
|
3881
|
+
const validPrereqs = [];
|
|
3882
|
+
for (const p of c.prerequisites || []) {
|
|
3883
|
+
if (!conceptIds.has(p)) {
|
|
3884
|
+
danglingPrerequisites.push({ conceptId: c.id, missingPrereqId: p });
|
|
3885
|
+
warnings.push(`Dangling prerequisite removed: ${c.id} -> ${p}`);
|
|
3886
|
+
} else {
|
|
3887
|
+
validPrereqs.push(p);
|
|
3888
|
+
}
|
|
3889
|
+
}
|
|
3890
|
+
c.prerequisites = validPrereqs;
|
|
3891
|
+
}
|
|
3892
|
+
const initialCycles = detectPrerequisiteCycles(concepts);
|
|
3893
|
+
let removedEdges = [];
|
|
3894
|
+
if (initialCycles.length > 0) {
|
|
3895
|
+
const broken = breakPrerequisiteCycles(concepts, initialCycles);
|
|
3896
|
+
concepts = broken.fixed;
|
|
3897
|
+
removedEdges = broken.removed;
|
|
3898
|
+
for (const r of removedEdges) {
|
|
3899
|
+
warnings.push(`Cycle broken: removed edge ${r.from} -> ${r.to}`);
|
|
3900
|
+
}
|
|
3901
|
+
}
|
|
3902
|
+
const coverage = auditSyllabusCoverage(concepts, syllabus);
|
|
3903
|
+
if (coverage.coverageRatio < 1) {
|
|
3904
|
+
warnings.push(`Syllabus coverage ${(coverage.coverageRatio * 100).toFixed(1)}% (${coverage.coveredTopics}/${coverage.totalTopics} topics). Uncovered: ${coverage.uncovered.join(", ")}`);
|
|
3905
|
+
}
|
|
3906
|
+
for (const c of concepts) {
|
|
3907
|
+
if (!c.ulo) warnings.push(`Concept ${c.id} missing ULO (WHAT + WHY)`);
|
|
3908
|
+
if (!c.cio) warnings.push(`Concept ${c.id} missing CIO (HOW mechanism)`);
|
|
3909
|
+
if (!c.sio || c.sio.length === 0) warnings.push(`Concept ${c.id} missing SIO implementation items`);
|
|
3910
|
+
}
|
|
3911
|
+
const valid = initialCycles.length === 0 && danglingPrerequisites.length === 0 && coverage.coverageRatio >= 0.8;
|
|
3912
|
+
const verifiedGraph = {
|
|
3913
|
+
...kg,
|
|
3914
|
+
concepts
|
|
3915
|
+
};
|
|
3916
|
+
const report = {
|
|
3917
|
+
valid,
|
|
3918
|
+
cycles: initialCycles,
|
|
3919
|
+
removedEdges,
|
|
3920
|
+
danglingPrerequisites,
|
|
3921
|
+
coverageRatio: coverage.coverageRatio,
|
|
3922
|
+
totalSyllabusTopics: coverage.totalTopics,
|
|
3923
|
+
coveredSyllabusTopics: coverage.coveredTopics,
|
|
3924
|
+
uncoveredTopics: coverage.uncovered,
|
|
3925
|
+
warnings
|
|
3926
|
+
};
|
|
3927
|
+
return { verifiedGraph, report };
|
|
3928
|
+
}
|
|
3929
|
+
|
|
3147
3930
|
// src/graph/loExemplars.ts
|
|
3148
3931
|
var ULO_EXEMPLARS = [
|
|
3149
3932
|
// Calibrated against production learning_objectives (UNIVERSAL rows, neutral)
|
|
@@ -3741,58 +4524,6 @@ async function standardizeConcepts(opts) {
|
|
|
3741
4524
|
}
|
|
3742
4525
|
|
|
3743
4526
|
// src/graph/knowledgeGraphPipeline.ts
|
|
3744
|
-
function detectCycles(concepts) {
|
|
3745
|
-
const cycles = [];
|
|
3746
|
-
const visited = /* @__PURE__ */ new Set();
|
|
3747
|
-
const inStack = /* @__PURE__ */ new Set();
|
|
3748
|
-
const path = [];
|
|
3749
|
-
function dfs(conceptId, prereqMap2) {
|
|
3750
|
-
if (inStack.has(conceptId)) {
|
|
3751
|
-
const cycleStart = path.indexOf(conceptId);
|
|
3752
|
-
if (cycleStart >= 0) {
|
|
3753
|
-
cycles.push([...path.slice(cycleStart), conceptId]);
|
|
3754
|
-
}
|
|
3755
|
-
return;
|
|
3756
|
-
}
|
|
3757
|
-
if (visited.has(conceptId)) return;
|
|
3758
|
-
visited.add(conceptId);
|
|
3759
|
-
inStack.add(conceptId);
|
|
3760
|
-
path.push(conceptId);
|
|
3761
|
-
const prereqs = prereqMap2.get(conceptId) || [];
|
|
3762
|
-
for (const prereq of prereqs) {
|
|
3763
|
-
dfs(prereq, prereqMap2);
|
|
3764
|
-
}
|
|
3765
|
-
path.pop();
|
|
3766
|
-
inStack.delete(conceptId);
|
|
3767
|
-
}
|
|
3768
|
-
const prereqMap = /* @__PURE__ */ new Map();
|
|
3769
|
-
for (const concept of concepts) {
|
|
3770
|
-
prereqMap.set(concept.id, concept.prerequisites || []);
|
|
3771
|
-
}
|
|
3772
|
-
for (const concept of concepts) {
|
|
3773
|
-
if (!visited.has(concept.id)) {
|
|
3774
|
-
dfs(concept.id, prereqMap);
|
|
3775
|
-
}
|
|
3776
|
-
}
|
|
3777
|
-
return cycles;
|
|
3778
|
-
}
|
|
3779
|
-
function breakCycles(concepts, cycles) {
|
|
3780
|
-
const removed = [];
|
|
3781
|
-
const fixed = concepts.map((c) => ({ ...c, prerequisites: [...c.prerequisites || []] }));
|
|
3782
|
-
for (const cycle of cycles) {
|
|
3783
|
-
if (cycle.length >= 2) {
|
|
3784
|
-
const breaker = cycle[cycle.length - 2];
|
|
3785
|
-
const target = cycle[cycle.length - 1];
|
|
3786
|
-
for (const concept of fixed) {
|
|
3787
|
-
if (concept.id === breaker) {
|
|
3788
|
-
concept.prerequisites = concept.prerequisites.filter((p) => p !== target);
|
|
3789
|
-
removed.push({ from: breaker, to: target });
|
|
3790
|
-
}
|
|
3791
|
-
}
|
|
3792
|
-
}
|
|
3793
|
-
}
|
|
3794
|
-
return { fixed, removed };
|
|
3795
|
-
}
|
|
3796
4527
|
function topologicalSort(concepts) {
|
|
3797
4528
|
const inDegree = /* @__PURE__ */ new Map();
|
|
3798
4529
|
const adjacency = /* @__PURE__ */ new Map();
|
|
@@ -3833,6 +4564,11 @@ async function generateKnowledgeGraph(options) {
|
|
|
3833
4564
|
onProgress?.(step, msg);
|
|
3834
4565
|
};
|
|
3835
4566
|
log("STEP_1", "Decomposing subject into concepts...");
|
|
4567
|
+
let parsedSyllabus = options.parsedSyllabus;
|
|
4568
|
+
if (!parsedSyllabus && syllabusText) {
|
|
4569
|
+
parsedSyllabus = parseSyllabus(syllabusText);
|
|
4570
|
+
log("STEP_1", `Parsed syllabus: ${parsedSyllabus.units.length} units, ${parsedSyllabus.allTopics.length} topics`);
|
|
4571
|
+
}
|
|
3836
4572
|
const client = createLlmClient(llmConfig);
|
|
3837
4573
|
const maxSyllabusChars = parseInt(process.env.KG_MAX_SYLLABUS_CHARS || "16000", 10);
|
|
3838
4574
|
let truncatedSyllabus = "";
|
|
@@ -3844,10 +4580,13 @@ async function generateKnowledgeGraph(options) {
|
|
|
3844
4580
|
truncatedSyllabus = syllabusText;
|
|
3845
4581
|
}
|
|
3846
4582
|
}
|
|
4583
|
+
const structuredTopicsGuide = parsedSyllabus && parsedSyllabus.allTopics.length > 0 ? `
|
|
4584
|
+
STRUCTURED SYLLABUS TOPICS (Every topic MUST map to at least one concept to ensure 100% syllabus coverage):
|
|
4585
|
+
` + parsedSyllabus.allTopics.map((t) => `- [${t.unitTitle}] ${t.id}: ${t.title} (Keywords: ${t.keywords.join(", ")})`).join("\n") + "\n" : "";
|
|
3847
4586
|
const systemPrompt = "You are an expert curriculum designer. Decompose the given subject into a structured knowledge graph with concepts, prerequisites, and problem types.\n\nCRITICAL RULES:\n1. Each concept MUST have the three depth layers, authored against this CONTRACT:\n - ulo (WHAT + WHY): bound to the CONCEPT's intrinsic NATURE. It explains what the\n concept is and why it exists \u2014 the problem it solves in principle. It must be\n TECHNOLOGY-AGNOSTIC: never name a concrete library, API, function, file format,\n tool or language feature. Typical assessment level: Remember / Understand.\n - cio (HOW): the MECHANISM \u2014 how the concept works and solves that problem, step\n by step. Still bound to the concept's nature: more concrete than the ULO, but\n NOT tied to one specific technology (no concrete API names). CIO capability is\n assessed INDIRECTLY, through completing contextual work, not in isolation.\n - sio (IMPLEMENTATION): an array of 2-4 items. EVERY item MUST reference concrete\n KEYWORDS/technologies (a real API, function, module, file, or named pattern) and\n describe a completable, context-specific implementation task. Application,\n Analysis and Creation skills are assessed INDIRECTLY through completing these\n items. There is NO fixed Bloom ceiling for a layer \u2014 but concreteness MUST\n strictly increase: SIO more technology-bound than CIO, CIO more concrete than ULO.\n - prerequisites: concept IDs that must be learned first (MUST NOT create cycles)\n - problem_types: 2-4 problem types with bloom_level and difficulty. Remember and\n Understand problem types test the ULO/CIO directly; Apply-and-above types are\n proxies for SIO completion in context.\n - techniques: methods, formulas, tools\n2. Prerequisite chains MUST be acyclic (no concept can depend on itself or create a loop)\n3. Concepts should be ordered by prerequisite dependency (topological sort)\n4. Problem types should range from Remember to Apply (not all at same level)\n5. Group concepts into categories (chapters/sections)\n6. ALL text in ENGLISH (even if input is Vietnamese)\n7. NEVER invent concepts not present in the syllabus\n8. Return JSON matching the KnowledgeGraphSchema";
|
|
3848
4587
|
const userPrompt = `Subject: ${subject}
|
|
3849
4588
|
${description ? `Description: ${description}` : ""}
|
|
3850
|
-
${truncatedSyllabus ? `Syllabus:
|
|
4589
|
+
${structuredTopicsGuide}${truncatedSyllabus ? `Syllabus:
|
|
3851
4590
|
${truncatedSyllabus}` : ""}
|
|
3852
4591
|
${gradeBand ? `Grade band: ${gradeBand[0]}-${gradeBand[1]}` : ""}
|
|
3853
4592
|
${domain ? `Domain: ${domain}` : ""}
|
|
@@ -3992,13 +4731,13 @@ Return JSON:
|
|
|
3992
4731
|
log("STEP_2_5", "Standardized: " + std.decisions.filter((d) => d.decision === "standardized").length + ", proposed new: " + std.decisions.filter((d) => d.decision === "proposed_new").length);
|
|
3993
4732
|
}
|
|
3994
4733
|
log("STEP_3", "Checking for prerequisite cycles...");
|
|
3995
|
-
const cycles =
|
|
4734
|
+
const cycles = detectPrerequisiteCycles(concepts);
|
|
3996
4735
|
if (cycles.length > 0) {
|
|
3997
4736
|
log("STEP_3", `Found ${cycles.length} cycle(s) in prerequisite chain \u2014 breaking cycles`);
|
|
3998
4737
|
for (const cycle of cycles) {
|
|
3999
4738
|
warnings.push(`Cycle detected: ${cycle.join(" \u2192 ")} \u2014 breaking at weakest edge`);
|
|
4000
4739
|
}
|
|
4001
|
-
const { fixed, removed } =
|
|
4740
|
+
const { fixed, removed } = breakPrerequisiteCycles(concepts, cycles);
|
|
4002
4741
|
for (let i = 0; i < concepts.length; i++) {
|
|
4003
4742
|
const fixedConcept = fixed.find((f) => f.id === concepts[i].id);
|
|
4004
4743
|
if (fixedConcept) {
|
|
@@ -4049,16 +4788,21 @@ Return JSON:
|
|
|
4049
4788
|
}
|
|
4050
4789
|
}
|
|
4051
4790
|
log("STEP_2", `Validated ${concepts.length} concepts, ${categories.length} categories, ${learningPath.length} learning path steps`);
|
|
4791
|
+
const rawGraph = {
|
|
4792
|
+
schema_version: 1,
|
|
4793
|
+
type: "knowledge_graph",
|
|
4794
|
+
subject: result.subject,
|
|
4795
|
+
concepts,
|
|
4796
|
+
categories,
|
|
4797
|
+
learning_path: learningPath
|
|
4798
|
+
};
|
|
4799
|
+
log("STEP_6", "Running Knowledge Graph verification & reverse coverage audit...");
|
|
4800
|
+
const { verifiedGraph, report } = verifyKnowledgeGraph(rawGraph, parsedSyllabus);
|
|
4801
|
+
warnings.push(...report.warnings);
|
|
4052
4802
|
return {
|
|
4053
|
-
knowledgeGraph:
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
subject: result.subject,
|
|
4057
|
-
concepts,
|
|
4058
|
-
categories,
|
|
4059
|
-
learning_path: learningPath
|
|
4060
|
-
},
|
|
4061
|
-
warnings
|
|
4803
|
+
knowledgeGraph: verifiedGraph,
|
|
4804
|
+
warnings,
|
|
4805
|
+
verificationReport: report
|
|
4062
4806
|
};
|
|
4063
4807
|
}
|
|
4064
4808
|
|
|
@@ -4437,563 +5181,247 @@ async function resolveConcepts(options) {
|
|
|
4437
5181
|
}
|
|
4438
5182
|
const effThreshold = SOURCE_THRESHOLDS[source] || threshold;
|
|
4439
5183
|
const topMatch = topMatches[0];
|
|
4440
|
-
if (topMatch.score >= effThreshold) {
|
|
4441
|
-
resolved.push({
|
|
4442
|
-
keyword,
|
|
4443
|
-
source,
|
|
4444
|
-
depth,
|
|
4445
|
-
concept_codes: topMatches.filter((m) => m.score >= effThreshold).map((m) => m.code),
|
|
4446
|
-
matches: topMatches
|
|
4447
|
-
});
|
|
4448
|
-
} else {
|
|
4449
|
-
proposed.push({
|
|
4450
|
-
keyword,
|
|
4451
|
-
source,
|
|
4452
|
-
depth,
|
|
4453
|
-
best_match: topMatch,
|
|
4454
|
-
reason: `Low confidence (${topMatch.score.toFixed(2)} < ${effThreshold})`
|
|
4455
|
-
});
|
|
4456
|
-
}
|
|
4457
|
-
}
|
|
4458
|
-
return {
|
|
4459
|
-
resolved,
|
|
4460
|
-
proposed,
|
|
4461
|
-
summary: {
|
|
4462
|
-
total_keywords: keywords.length,
|
|
4463
|
-
resolved_count: resolved.length,
|
|
4464
|
-
proposed_count: proposed.length,
|
|
4465
|
-
search_mode
|
|
4466
|
-
}
|
|
4467
|
-
};
|
|
4468
|
-
}
|
|
4469
|
-
|
|
4470
|
-
// src/assembly/roadmapAssembler.ts
|
|
4471
|
-
function calculateOverhead(rawMinutes, overheadFactor) {
|
|
4472
|
-
return Math.ceil(rawMinutes * overheadFactor);
|
|
4473
|
-
}
|
|
4474
|
-
function splitFeatureIntoMilestones(feature, featureConcepts, maxMinutes, overheadFactor) {
|
|
4475
|
-
const steps = feature.steps || [];
|
|
4476
|
-
if (steps.length === 0) return [];
|
|
4477
|
-
const featureId = feature.id || "F_UNK";
|
|
4478
|
-
const stepMinutes2 = steps.map((s) => s.effort?.estimated_minutes || 15);
|
|
4479
|
-
const totalRawMinutes = stepMinutes2.reduce((a, b) => a + b, 0);
|
|
4480
|
-
if (calculateOverhead(totalRawMinutes, overheadFactor) <= maxMinutes) {
|
|
4481
|
-
return [buildMilestone(feature, featureConcepts, steps, stepMinutes2, featureId, 1)];
|
|
4482
|
-
}
|
|
4483
|
-
const milestones = [];
|
|
4484
|
-
let currentSteps = [];
|
|
4485
|
-
let currentMinutes = [];
|
|
4486
|
-
let milestoneIndex = 1;
|
|
4487
|
-
for (let i = 0; i < steps.length; i++) {
|
|
4488
|
-
const step = steps[i];
|
|
4489
|
-
const mins = stepMinutes2[i];
|
|
4490
|
-
const currentTotal = currentMinutes.reduce((a, b) => a + b, 0);
|
|
4491
|
-
if (calculateOverhead(currentTotal + mins, overheadFactor) > maxMinutes && currentSteps.length > 0) {
|
|
4492
|
-
milestones.push(buildMilestone(
|
|
4493
|
-
feature,
|
|
4494
|
-
featureConcepts,
|
|
4495
|
-
currentSteps,
|
|
4496
|
-
currentMinutes,
|
|
4497
|
-
featureId,
|
|
4498
|
-
milestoneIndex
|
|
4499
|
-
));
|
|
4500
|
-
milestoneIndex++;
|
|
4501
|
-
currentSteps = [];
|
|
4502
|
-
currentMinutes = [];
|
|
4503
|
-
}
|
|
4504
|
-
currentSteps.push(step);
|
|
4505
|
-
currentMinutes.push(mins);
|
|
4506
|
-
}
|
|
4507
|
-
if (currentSteps.length > 0) {
|
|
4508
|
-
milestones.push(buildMilestone(
|
|
4509
|
-
feature,
|
|
4510
|
-
featureConcepts,
|
|
4511
|
-
currentSteps,
|
|
4512
|
-
currentMinutes,
|
|
4513
|
-
featureId,
|
|
4514
|
-
milestoneIndex
|
|
4515
|
-
));
|
|
4516
|
-
}
|
|
4517
|
-
return milestones;
|
|
4518
|
-
}
|
|
4519
|
-
function buildMilestone(feature, featureConcepts, steps, stepMinutes2, featureId, milestoneIndex) {
|
|
4520
|
-
const rawMinutes = stepMinutes2.reduce((a, b) => a + b, 0);
|
|
4521
|
-
const levels = steps.map((s) => s.completion_level || "mvp");
|
|
4522
|
-
const completionLevel = levels.includes("extend") ? "extend" : levels.includes("mvp") ? "mvp" : "base";
|
|
4523
|
-
const milestoneConcepts = /* @__PURE__ */ new Set();
|
|
4524
|
-
const milestoneKeywords = /* @__PURE__ */ new Set();
|
|
4525
|
-
for (const step of steps) {
|
|
4526
|
-
const stepConceptCodes = step.concept_codes || [];
|
|
4527
|
-
for (const code of stepConceptCodes) {
|
|
4528
|
-
milestoneConcepts.add(code);
|
|
4529
|
-
}
|
|
4530
|
-
const stepKws = [...step.keywords || [], ...step.api_usage || []];
|
|
4531
|
-
for (const cm of featureConcepts) {
|
|
4532
|
-
if (stepKws.includes(cm.keyword)) {
|
|
4533
|
-
milestoneConcepts.add(cm.concept_code);
|
|
4534
|
-
milestoneKeywords.add(cm.keyword);
|
|
4535
|
-
}
|
|
4536
|
-
}
|
|
4537
|
-
}
|
|
4538
|
-
const depthCounts = { ulo: 0, cio: 0, sio: 0 };
|
|
4539
|
-
for (const cm of featureConcepts) {
|
|
4540
|
-
if (milestoneKeywords.has(cm.keyword)) {
|
|
4541
|
-
depthCounts[cm.depth]++;
|
|
4542
|
-
}
|
|
4543
|
-
}
|
|
4544
|
-
const dominantDepth = depthCounts.sio >= depthCounts.cio && depthCounts.sio >= depthCounts.ulo ? "sio" : depthCounts.cio >= depthCounts.ulo ? "cio" : "ulo";
|
|
4545
|
-
return {
|
|
4546
|
-
id: milestoneIndex === 1 ? featureId : `${featureId}-M${milestoneIndex}`,
|
|
4547
|
-
name: milestoneIndex === 1 ? feature.name || featureId : `${feature.name || featureId} (Part ${milestoneIndex})`,
|
|
4548
|
-
description: feature.description || "",
|
|
4549
|
-
feature_id: featureId,
|
|
4550
|
-
feature_name: feature.name || "",
|
|
4551
|
-
concept_code: [...milestoneConcepts].join(", "),
|
|
4552
|
-
depth: dominantDepth,
|
|
4553
|
-
all_keywords: [],
|
|
4554
|
-
new_keywords: [],
|
|
4555
|
-
prerequisite_keywords: [],
|
|
4556
|
-
steps,
|
|
4557
|
-
estimated_minutes: rawMinutes,
|
|
4558
|
-
adjusted_minutes: rawMinutes,
|
|
4559
|
-
// placeholder — assembleRoadmap() applies actual overheadFactor
|
|
4560
|
-
completion_level: completionLevel
|
|
4561
|
-
};
|
|
4562
|
-
}
|
|
4563
|
-
function buildKeywordMap(featureConcepts) {
|
|
4564
|
-
const map = /* @__PURE__ */ new Map();
|
|
4565
|
-
for (const mappings of featureConcepts.values()) {
|
|
4566
|
-
for (const m of mappings) {
|
|
4567
|
-
const existing = map.get(m.concept_code) || [];
|
|
4568
|
-
if (!existing.includes(m.keyword)) {
|
|
4569
|
-
existing.push(m.keyword);
|
|
4570
|
-
map.set(m.concept_code, existing);
|
|
4571
|
-
}
|
|
4572
|
-
}
|
|
4573
|
-
}
|
|
4574
|
-
return map;
|
|
4575
|
-
}
|
|
4576
|
-
function attachKeywordsToMilestones(milestones, keywordMap) {
|
|
4577
|
-
const cumulativeKeywords = /* @__PURE__ */ new Set();
|
|
4578
|
-
for (const milestone of milestones) {
|
|
4579
|
-
const allKw = /* @__PURE__ */ new Set();
|
|
4580
|
-
const conceptCodes = milestone.concept_code.split(",").map((c) => c.trim()).filter(Boolean);
|
|
4581
|
-
for (const code of conceptCodes) {
|
|
4582
|
-
const kws = keywordMap.get(code) || [];
|
|
4583
|
-
for (const kw of kws) allKw.add(kw);
|
|
4584
|
-
}
|
|
4585
|
-
milestone.all_keywords = Array.from(allKw).sort();
|
|
4586
|
-
const newKw = [];
|
|
4587
|
-
const prereqKw = [];
|
|
4588
|
-
for (const kw of milestone.all_keywords) {
|
|
4589
|
-
if (cumulativeKeywords.has(kw)) {
|
|
4590
|
-
prereqKw.push(kw);
|
|
4591
|
-
} else {
|
|
4592
|
-
newKw.push(kw);
|
|
4593
|
-
cumulativeKeywords.add(kw);
|
|
4594
|
-
}
|
|
4595
|
-
}
|
|
4596
|
-
milestone.new_keywords = newKw;
|
|
4597
|
-
milestone.prerequisite_keywords = prereqKw;
|
|
4598
|
-
}
|
|
4599
|
-
return milestones;
|
|
4600
|
-
}
|
|
4601
|
-
function groupIntoPhases(milestones, sessionMinutes) {
|
|
4602
|
-
const phases = [];
|
|
4603
|
-
let currentMilestones = [];
|
|
4604
|
-
let currentMinutes = 0;
|
|
4605
|
-
let phaseNum = 1;
|
|
4606
|
-
for (const milestone of milestones) {
|
|
4607
|
-
const milestoneMinutes = milestone.adjusted_minutes;
|
|
4608
|
-
if (currentMinutes + milestoneMinutes > sessionMinutes && currentMilestones.length > 0) {
|
|
4609
|
-
phases.push(buildPhase(currentMilestones, phaseNum));
|
|
4610
|
-
phaseNum++;
|
|
4611
|
-
currentMilestones = [];
|
|
4612
|
-
currentMinutes = 0;
|
|
4613
|
-
}
|
|
4614
|
-
currentMilestones.push(milestone);
|
|
4615
|
-
currentMinutes += milestoneMinutes;
|
|
4616
|
-
}
|
|
4617
|
-
if (currentMilestones.length > 0) {
|
|
4618
|
-
phases.push(buildPhase(currentMilestones, phaseNum));
|
|
4619
|
-
}
|
|
4620
|
-
return phases;
|
|
4621
|
-
}
|
|
4622
|
-
function buildPhase(milestones, phaseNum) {
|
|
4623
|
-
const totalMinutes = milestones.reduce((sum, m) => sum + m.adjusted_minutes, 0);
|
|
4624
|
-
const depths = milestones.map((m) => m.depth);
|
|
4625
|
-
const uniqueDepths = [...new Set(depths)];
|
|
4626
|
-
const progression = uniqueDepths.map((d) => d.toUpperCase()).join(" \u2192 ");
|
|
4627
|
-
return {
|
|
4628
|
-
phase_id: phaseNum,
|
|
4629
|
-
phase_name: `Phase ${phaseNum}`,
|
|
4630
|
-
milestones,
|
|
4631
|
-
total_minutes: totalMinutes,
|
|
4632
|
-
concept_progression: progression
|
|
4633
|
-
};
|
|
4634
|
-
}
|
|
4635
|
-
function assembleRoadmap(options) {
|
|
4636
|
-
const {
|
|
4637
|
-
projectGraph,
|
|
4638
|
-
featureConcepts = /* @__PURE__ */ new Map(),
|
|
4639
|
-
sessionMinutes = 90,
|
|
4640
|
-
overheadFactor = 1.15,
|
|
4641
|
-
maxMilestoneMinutes = 120
|
|
4642
|
-
} = options;
|
|
4643
|
-
const features = projectGraph.features || [];
|
|
4644
|
-
const keywordMap = buildKeywordMap(featureConcepts);
|
|
4645
|
-
const allMilestones = [];
|
|
4646
|
-
for (const feature of features) {
|
|
4647
|
-
const fid = feature.id || "F_UNK";
|
|
4648
|
-
const fConcepts = featureConcepts.get(fid) || [];
|
|
4649
|
-
const milestones = splitFeatureIntoMilestones(
|
|
4650
|
-
feature,
|
|
4651
|
-
fConcepts,
|
|
4652
|
-
maxMilestoneMinutes,
|
|
4653
|
-
overheadFactor
|
|
4654
|
-
);
|
|
4655
|
-
allMilestones.push(...milestones);
|
|
4656
|
-
}
|
|
4657
|
-
for (const milestone of allMilestones) {
|
|
4658
|
-
milestone.adjusted_minutes = calculateOverhead(milestone.estimated_minutes, overheadFactor);
|
|
4659
|
-
}
|
|
4660
|
-
attachKeywordsToMilestones(allMilestones, keywordMap);
|
|
4661
|
-
const phases = groupIntoPhases(allMilestones, sessionMinutes);
|
|
4662
|
-
const totalEstimatedMinutes = allMilestones.reduce((sum, m) => sum + m.estimated_minutes, 0);
|
|
4663
|
-
const totalAdjustedMinutes = allMilestones.reduce((sum, m) => sum + m.adjusted_minutes, 0);
|
|
4664
|
-
return {
|
|
4665
|
-
project: projectGraph.project,
|
|
4666
|
-
product: projectGraph.product,
|
|
4667
|
-
phases,
|
|
4668
|
-
feature_concepts: featureConcepts,
|
|
4669
|
-
time_budget: {
|
|
4670
|
-
session_minutes: sessionMinutes,
|
|
4671
|
-
overhead_factor: overheadFactor,
|
|
4672
|
-
total_estimated_minutes: totalEstimatedMinutes,
|
|
4673
|
-
total_adjusted_minutes: totalAdjustedMinutes
|
|
4674
|
-
}
|
|
4675
|
-
};
|
|
4676
|
-
}
|
|
4677
|
-
|
|
4678
|
-
// src/feed/curriculumFeedEmitter.ts
|
|
4679
|
-
var BLOOM_RANK = { Remember: 0, Understand: 1, Apply: 2, Analyze: 3, Evaluate: 4, Create: 5 };
|
|
4680
|
-
var pad22 = (n) => String(n).padStart(2, "0");
|
|
4681
|
-
var arr = (v) => Array.isArray(v) ? v : [];
|
|
4682
|
-
var str = (v, d = "") => typeof v === "string" ? v : d;
|
|
4683
|
-
var num = (v, d) => typeof v === "number" && Number.isFinite(v) ? v : d;
|
|
4684
|
-
var strs = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
|
|
4685
|
-
function stepMinutes(step) {
|
|
4686
|
-
const effort = step.effort ?? {};
|
|
4687
|
-
return Math.max(0, Math.round(num(effort.estimated_minutes, num(step.estimated_minutes, 45))));
|
|
4688
|
-
}
|
|
4689
|
-
function newKeywordsInOrder(nodes) {
|
|
4690
|
-
const seen = /* @__PURE__ */ new Set();
|
|
4691
|
-
for (const n of nodes) {
|
|
4692
|
-
const all = n.keywords.all;
|
|
4693
|
-
n.keywords.new = [];
|
|
4694
|
-
n.keywords.prerequisite = [];
|
|
4695
|
-
for (const k of all) {
|
|
4696
|
-
if (seen.has(k)) n.keywords.prerequisite.push(k);
|
|
4697
|
-
else n.keywords.new.push(k);
|
|
4698
|
-
seen.add(k);
|
|
5184
|
+
if (topMatch.score >= effThreshold) {
|
|
5185
|
+
resolved.push({
|
|
5186
|
+
keyword,
|
|
5187
|
+
source,
|
|
5188
|
+
depth,
|
|
5189
|
+
concept_codes: topMatches.filter((m) => m.score >= effThreshold).map((m) => m.code),
|
|
5190
|
+
matches: topMatches
|
|
5191
|
+
});
|
|
5192
|
+
} else {
|
|
5193
|
+
proposed.push({
|
|
5194
|
+
keyword,
|
|
5195
|
+
source,
|
|
5196
|
+
depth,
|
|
5197
|
+
best_match: topMatch,
|
|
5198
|
+
reason: `Low confidence (${topMatch.score.toFixed(2)} < ${effThreshold})`
|
|
5199
|
+
});
|
|
4699
5200
|
}
|
|
4700
5201
|
}
|
|
4701
|
-
}
|
|
4702
|
-
function feedNodeFromStep(step, featureId) {
|
|
4703
|
-
const id = str(step.id);
|
|
4704
|
-
return {
|
|
4705
|
-
id,
|
|
4706
|
-
kind: "product_step",
|
|
4707
|
-
name: str(step.name, id),
|
|
4708
|
-
concept_codes: strs(step.concept_codes),
|
|
4709
|
-
keywords: { all: strs(step.keywords), new: [], prerequisite: [] },
|
|
4710
|
-
estimated_minutes: stepMinutes(step),
|
|
4711
|
-
bloom_hint: "Apply",
|
|
4712
|
-
depth_hint: null,
|
|
4713
|
-
completion_level: "standard",
|
|
4714
|
-
scaffolding: { scaffold_candidates: [], core_value_minutes: stepMinutes(step) },
|
|
4715
|
-
references: arr(step.files).map((f) => ({ file: str(f), evidence: "step file" })).filter((f) => f.file),
|
|
4716
|
-
feature_id: featureId,
|
|
4717
|
-
concept_ref: null,
|
|
4718
|
-
phase_id: "",
|
|
4719
|
-
introduce_aspect: null
|
|
4720
|
-
};
|
|
4721
|
-
}
|
|
4722
|
-
function feedNodeFromConcept(concept) {
|
|
4723
|
-
const id = str(concept.id);
|
|
4724
|
-
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]);
|
|
4725
|
-
const topBloom = pts[pts.length - 1] ?? "Understand";
|
|
4726
5202
|
return {
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
completion_level: "standard",
|
|
4736
|
-
scaffolding: { scaffold_candidates: [], core_value_minutes: 0 },
|
|
4737
|
-
references: [],
|
|
4738
|
-
feature_id: null,
|
|
4739
|
-
concept_ref: id,
|
|
4740
|
-
phase_id: "",
|
|
4741
|
-
introduce_aspect: null
|
|
5203
|
+
resolved,
|
|
5204
|
+
proposed,
|
|
5205
|
+
summary: {
|
|
5206
|
+
total_keywords: keywords.length,
|
|
5207
|
+
resolved_count: resolved.length,
|
|
5208
|
+
proposed_count: proposed.length,
|
|
5209
|
+
search_mode
|
|
5210
|
+
}
|
|
4742
5211
|
};
|
|
4743
5212
|
}
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
for (let i = 0; i < steps.length; i++) {
|
|
4749
|
-
const stepNode = feedNodeFromStep(steps[i], fid);
|
|
4750
|
-
if (!stepNode.id) continue;
|
|
4751
|
-
stepIds.push(stepNode.id);
|
|
4752
|
-
nodes.push(stepNode);
|
|
4753
|
-
if (prev) edges.push({ from: prev, to: stepNode.id, kind: "task", reason: "feature build order" });
|
|
4754
|
-
prev = stepNode.id;
|
|
4755
|
-
}
|
|
4756
|
-
for (const dep of strs(f.depends_on)) {
|
|
4757
|
-
const depFirst = nodes.find((n) => n.feature_id === dep);
|
|
4758
|
-
if (depFirst && stepIds[0]) edges.push({ from: depFirst.id, to: stepIds[0], kind: "task", reason: "feature depends_on " + dep });
|
|
4759
|
-
}
|
|
4760
|
-
if (stepIds.length > 0) groupings.push({ id: "G-" + fid, name: str(f.name, fid), node_ids: stepIds, rationale: str(f.description, "") });
|
|
4761
|
-
return stepIds;
|
|
5213
|
+
|
|
5214
|
+
// src/assembly/roadmapAssembler.ts
|
|
5215
|
+
function calculateOverhead(rawMinutes, overheadFactor) {
|
|
5216
|
+
return Math.ceil(rawMinutes * overheadFactor);
|
|
4762
5217
|
}
|
|
4763
|
-
function
|
|
4764
|
-
const
|
|
4765
|
-
|
|
4766
|
-
const
|
|
4767
|
-
const
|
|
4768
|
-
const
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
const conceptRecs = arr(kg.concepts);
|
|
4772
|
-
const conceptById = new Map(conceptRecs.map((c) => [str(c.id), c]));
|
|
4773
|
-
if (rawPhases.length === 0) {
|
|
4774
|
-
for (const c of conceptRecs) {
|
|
4775
|
-
const node = feedNodeFromConcept(c);
|
|
4776
|
-
nodes.push(node);
|
|
4777
|
-
for (const p of strs(c.prerequisites)) edges.push({ from: p, to: node.id, kind: "knowledge", reason: "concept prerequisite" });
|
|
4778
|
-
}
|
|
4779
|
-
features.forEach((f, fi) => addFeatureGroup(f, str(f.id, "F" + pad22(fi + 1)), nodes, edges, groupings));
|
|
4780
|
-
linkHybridKnowledgeEdges(hybrid, nodes, edges);
|
|
4781
|
-
newKeywordsInOrder(nodes);
|
|
4782
|
-
return validateCurriculumFeed({
|
|
4783
|
-
schema_version: 2,
|
|
4784
|
-
source: { graph_type: "hybrid_graph", warnings: opts.warnings ?? [], hallucination_count: opts.hallucinationCount ?? 0 },
|
|
4785
|
-
learning_nodes: nodes,
|
|
4786
|
-
dependency_edges: edges,
|
|
4787
|
-
suggested_groupings: groupings,
|
|
4788
|
-
phases: []
|
|
4789
|
-
});
|
|
4790
|
-
}
|
|
4791
|
-
const featureOrder = /* @__PURE__ */ new Map();
|
|
4792
|
-
features.forEach((f, fi) => featureOrder.set(str(f.id, "F" + pad22(fi + 1)), fi));
|
|
4793
|
-
const stepIdsByFeature = /* @__PURE__ */ new Map();
|
|
4794
|
-
for (const f of features) {
|
|
4795
|
-
const fid = str(f.id);
|
|
4796
|
-
stepIdsByFeature.set(fid, arr(f.steps).map((s) => str(s.id)).filter(Boolean));
|
|
4797
|
-
}
|
|
4798
|
-
const emitCtx = [];
|
|
4799
|
-
const emittedConcepts = /* @__PURE__ */ new Set();
|
|
4800
|
-
const linkList = arr(hybrid.links);
|
|
4801
|
-
for (const rp of rawPhases) {
|
|
4802
|
-
const pid = str(rp.id);
|
|
4803
|
-
const order = num(rp.order, emitCtx.length + 1);
|
|
4804
|
-
const conceptOrder = [];
|
|
4805
|
-
const intros = arr(rp.introduces);
|
|
4806
|
-
const news = intros.filter((i) => str(i.aspect, "new") === "new");
|
|
4807
|
-
const advanced = intros.filter((i) => str(i.aspect) === "advanced");
|
|
4808
|
-
for (const intro of [...news, ...advanced]) {
|
|
4809
|
-
const cid = str(intro.concept_id);
|
|
4810
|
-
const c = conceptById.get(cid);
|
|
4811
|
-
if (!c) {
|
|
4812
|
-
throw new Error("Phase " + pid + " introduces unknown concept " + cid + " - fail-closed");
|
|
4813
|
-
}
|
|
4814
|
-
if (str(intro.aspect, "new") === "advanced" && !emittedConcepts.has(cid)) {
|
|
4815
|
-
throw new Error("Phase " + pid + " advanced-revisits concept " + cid + " that was never emitted - fail-closed");
|
|
4816
|
-
}
|
|
4817
|
-
if (!emittedConcepts.has(cid)) {
|
|
4818
|
-
const node = feedNodeFromConcept(c);
|
|
4819
|
-
node.phase_id = pid;
|
|
4820
|
-
node.introduce_aspect = "new";
|
|
4821
|
-
nodes.push(node);
|
|
4822
|
-
for (const p of strs(c.prerequisites)) {
|
|
4823
|
-
if (!emittedConcepts.has(p)) {
|
|
4824
|
-
throw new Error("Concept " + cid + " (phase " + pid + ") has prerequisite " + p + " that is not introduced in an earlier phase - fail-closed");
|
|
4825
|
-
}
|
|
4826
|
-
edges.push({ from: p, to: cid, kind: "knowledge", reason: "concept prerequisite" });
|
|
4827
|
-
}
|
|
4828
|
-
emittedConcepts.add(cid);
|
|
4829
|
-
conceptOrder.push(cid);
|
|
4830
|
-
} else {
|
|
4831
|
-
const revisitId = cid + "__ADV" + pad22(order);
|
|
4832
|
-
const node = feedNodeFromConcept(c);
|
|
4833
|
-
node.id = revisitId;
|
|
4834
|
-
node.name = str(c.name, cid) + " (advanced)";
|
|
4835
|
-
node.phase_id = pid;
|
|
4836
|
-
node.introduce_aspect = "advanced";
|
|
4837
|
-
node.estimated_minutes = Math.max(15, Math.round(node.estimated_minutes * 0.5));
|
|
4838
|
-
node.concept_codes = [str(c.code) || cid];
|
|
4839
|
-
edges.push({ from: cid, to: revisitId, kind: "knowledge", reason: "advanced revisit of " + cid + " at phase " + pid });
|
|
4840
|
-
nodes.push(node);
|
|
4841
|
-
conceptOrder.push(revisitId);
|
|
4842
|
-
}
|
|
4843
|
-
}
|
|
4844
|
-
const featureIds = strs(rp.feature_ids).filter((fid) => featureOrder.has(fid)).sort((a, b) => featureOrder.get(a) - featureOrder.get(b));
|
|
4845
|
-
emitCtx.push({ id: pid, order, name: str(rp.name, pid), productCompletion: str(rp.product_completion), conceptOrder, featureIds });
|
|
5218
|
+
function splitFeatureIntoMilestones(feature, featureConcepts, maxMinutes, overheadFactor) {
|
|
5219
|
+
const steps = feature.steps || [];
|
|
5220
|
+
if (steps.length === 0) return [];
|
|
5221
|
+
const featureId = feature.id || "F_UNK";
|
|
5222
|
+
const stepMinutes2 = steps.map((s) => s.effort?.estimated_minutes || 15);
|
|
5223
|
+
const totalRawMinutes = stepMinutes2.reduce((a, b) => a + b, 0);
|
|
5224
|
+
if (calculateOverhead(totalRawMinutes, overheadFactor) <= maxMinutes) {
|
|
5225
|
+
return [buildMilestone(feature, featureConcepts, steps, stepMinutes2, featureId, 1)];
|
|
4846
5226
|
}
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
5227
|
+
const milestones = [];
|
|
5228
|
+
let currentSteps = [];
|
|
5229
|
+
let currentMinutes = [];
|
|
5230
|
+
let milestoneIndex = 1;
|
|
5231
|
+
for (let i = 0; i < steps.length; i++) {
|
|
5232
|
+
const step = steps[i];
|
|
5233
|
+
const mins = stepMinutes2[i];
|
|
5234
|
+
const currentTotal = currentMinutes.reduce((a, b) => a + b, 0);
|
|
5235
|
+
if (calculateOverhead(currentTotal + mins, overheadFactor) > maxMinutes && currentSteps.length > 0) {
|
|
5236
|
+
milestones.push(buildMilestone(
|
|
5237
|
+
feature,
|
|
5238
|
+
featureConcepts,
|
|
5239
|
+
currentSteps,
|
|
5240
|
+
currentMinutes,
|
|
5241
|
+
featureId,
|
|
5242
|
+
milestoneIndex
|
|
5243
|
+
));
|
|
5244
|
+
milestoneIndex++;
|
|
5245
|
+
currentSteps = [];
|
|
5246
|
+
currentMinutes = [];
|
|
4851
5247
|
}
|
|
5248
|
+
currentSteps.push(step);
|
|
5249
|
+
currentMinutes.push(mins);
|
|
4852
5250
|
}
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
5251
|
+
if (currentSteps.length > 0) {
|
|
5252
|
+
milestones.push(buildMilestone(
|
|
5253
|
+
feature,
|
|
5254
|
+
featureConcepts,
|
|
5255
|
+
currentSteps,
|
|
5256
|
+
currentMinutes,
|
|
5257
|
+
featureId,
|
|
5258
|
+
milestoneIndex
|
|
5259
|
+
));
|
|
4857
5260
|
}
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
5261
|
+
return milestones;
|
|
5262
|
+
}
|
|
5263
|
+
function buildMilestone(feature, featureConcepts, steps, stepMinutes2, featureId, milestoneIndex) {
|
|
5264
|
+
const rawMinutes = stepMinutes2.reduce((a, b) => a + b, 0);
|
|
5265
|
+
const levels = steps.map((s) => s.completion_level || "mvp");
|
|
5266
|
+
const completionLevel = levels.includes("extend") ? "extend" : levels.includes("mvp") ? "mvp" : "base";
|
|
5267
|
+
const milestoneConcepts = /* @__PURE__ */ new Set();
|
|
5268
|
+
const milestoneKeywords = /* @__PURE__ */ new Set();
|
|
5269
|
+
for (const step of steps) {
|
|
5270
|
+
const stepConceptCodes = step.concept_codes || [];
|
|
5271
|
+
for (const code of stepConceptCodes) {
|
|
5272
|
+
milestoneConcepts.add(code);
|
|
4869
5273
|
}
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
if (edgeSeen.has(key)) return;
|
|
4876
|
-
edgeSeen.add(key);
|
|
4877
|
-
edges.push({ from, to, kind: "knowledge", reason });
|
|
4878
|
-
};
|
|
4879
|
-
for (const l of linkList) {
|
|
4880
|
-
const cid = str(l.concept_id);
|
|
4881
|
-
const fid = str(l.feature_id);
|
|
4882
|
-
if (!conceptById.has(cid)) continue;
|
|
4883
|
-
const firstStep = (stepIdsByFeature.get(fid) || [])[0];
|
|
4884
|
-
if (!firstStep) continue;
|
|
4885
|
-
const depth = str(l.depth, "cio");
|
|
4886
|
-
pushEdge(cid, firstStep, str(l.relationship, "hybrid link") + " [" + depth + "]");
|
|
4887
|
-
for (const n of nodes) {
|
|
4888
|
-
if (n.kind === "concept" && n.concept_ref === cid && n.introduce_aspect === "advanced" && n.phase_id === str(l.phase_id)) {
|
|
4889
|
-
pushEdge(n.id, firstStep, "advanced application [" + depth + "]");
|
|
5274
|
+
const stepKws = [...step.keywords || [], ...step.api_usage || []];
|
|
5275
|
+
for (const cm of featureConcepts) {
|
|
5276
|
+
if (stepKws.includes(cm.keyword)) {
|
|
5277
|
+
milestoneConcepts.add(cm.concept_code);
|
|
5278
|
+
milestoneKeywords.add(cm.keyword);
|
|
4890
5279
|
}
|
|
4891
5280
|
}
|
|
4892
5281
|
}
|
|
4893
|
-
|
|
4894
|
-
const
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
if (!introducer.has(key)) introducer.set(key, n.id);
|
|
5282
|
+
const depthCounts = { ulo: 0, cio: 0, sio: 0 };
|
|
5283
|
+
for (const cm of featureConcepts) {
|
|
5284
|
+
if (milestoneKeywords.has(cm.keyword)) {
|
|
5285
|
+
depthCounts[cm.depth]++;
|
|
5286
|
+
}
|
|
4899
5287
|
}
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
5288
|
+
const dominantDepth = depthCounts.sio >= depthCounts.cio && depthCounts.sio >= depthCounts.ulo ? "sio" : depthCounts.cio >= depthCounts.ulo ? "cio" : "ulo";
|
|
5289
|
+
return {
|
|
5290
|
+
id: milestoneIndex === 1 ? featureId : `${featureId}-M${milestoneIndex}`,
|
|
5291
|
+
name: milestoneIndex === 1 ? feature.name || featureId : `${feature.name || featureId} (Part ${milestoneIndex})`,
|
|
5292
|
+
description: feature.description || "",
|
|
5293
|
+
feature_id: featureId,
|
|
5294
|
+
feature_name: feature.name || "",
|
|
5295
|
+
concept_code: [...milestoneConcepts].join(", "),
|
|
5296
|
+
depth: dominantDepth,
|
|
5297
|
+
all_keywords: [],
|
|
5298
|
+
new_keywords: [],
|
|
5299
|
+
prerequisite_keywords: [],
|
|
5300
|
+
steps,
|
|
5301
|
+
estimated_minutes: rawMinutes,
|
|
5302
|
+
adjusted_minutes: rawMinutes,
|
|
5303
|
+
// placeholder — assembleRoadmap() applies actual overheadFactor
|
|
5304
|
+
completion_level: completionLevel
|
|
5305
|
+
};
|
|
5306
|
+
}
|
|
5307
|
+
function buildKeywordMap(featureConcepts) {
|
|
5308
|
+
const map = /* @__PURE__ */ new Map();
|
|
5309
|
+
for (const mappings of featureConcepts.values()) {
|
|
5310
|
+
for (const m of mappings) {
|
|
5311
|
+
const existing = map.get(m.concept_code) || [];
|
|
5312
|
+
if (!existing.includes(m.keyword)) {
|
|
5313
|
+
existing.push(m.keyword);
|
|
5314
|
+
map.set(m.concept_code, existing);
|
|
5315
|
+
}
|
|
4905
5316
|
}
|
|
4906
5317
|
}
|
|
4907
|
-
|
|
4908
|
-
id: ctx.id,
|
|
4909
|
-
order: ctx.order,
|
|
4910
|
-
name: ctx.name,
|
|
4911
|
-
product_completion: ctx.productCompletion || "Phase " + (i + 1) + " deliverable",
|
|
4912
|
-
node_ids: phaseNodeIds[i]
|
|
4913
|
-
}));
|
|
4914
|
-
return validateCurriculumFeed({
|
|
4915
|
-
schema_version: 2,
|
|
4916
|
-
source: { graph_type: "hybrid_graph", warnings: opts.warnings ?? [], hallucination_count: opts.hallucinationCount ?? 0 },
|
|
4917
|
-
learning_nodes: nodes,
|
|
4918
|
-
dependency_edges: edges,
|
|
4919
|
-
suggested_groupings: groupings,
|
|
4920
|
-
phases
|
|
4921
|
-
});
|
|
5318
|
+
return map;
|
|
4922
5319
|
}
|
|
4923
|
-
function
|
|
4924
|
-
const
|
|
4925
|
-
for (const
|
|
4926
|
-
const
|
|
4927
|
-
const
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
5320
|
+
function attachKeywordsToMilestones(milestones, keywordMap) {
|
|
5321
|
+
const cumulativeKeywords = /* @__PURE__ */ new Set();
|
|
5322
|
+
for (const milestone of milestones) {
|
|
5323
|
+
const allKw = /* @__PURE__ */ new Set();
|
|
5324
|
+
const conceptCodes = milestone.concept_code.split(",").map((c) => c.trim()).filter(Boolean);
|
|
5325
|
+
for (const code of conceptCodes) {
|
|
5326
|
+
const kws = keywordMap.get(code) || [];
|
|
5327
|
+
for (const kw of kws) allKw.add(kw);
|
|
5328
|
+
}
|
|
5329
|
+
milestone.all_keywords = Array.from(allKw).sort();
|
|
5330
|
+
const newKw = [];
|
|
5331
|
+
const prereqKw = [];
|
|
5332
|
+
for (const kw of milestone.all_keywords) {
|
|
5333
|
+
if (cumulativeKeywords.has(kw)) {
|
|
5334
|
+
prereqKw.push(kw);
|
|
5335
|
+
} else {
|
|
5336
|
+
newKw.push(kw);
|
|
5337
|
+
cumulativeKeywords.add(kw);
|
|
4935
5338
|
}
|
|
4936
5339
|
}
|
|
5340
|
+
milestone.new_keywords = newKw;
|
|
5341
|
+
milestone.prerequisite_keywords = prereqKw;
|
|
4937
5342
|
}
|
|
5343
|
+
return milestones;
|
|
4938
5344
|
}
|
|
4939
|
-
function
|
|
4940
|
-
const
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
5345
|
+
function groupIntoPhases(milestones, sessionMinutes) {
|
|
5346
|
+
const phases = [];
|
|
5347
|
+
let currentMilestones = [];
|
|
5348
|
+
let currentMinutes = 0;
|
|
5349
|
+
let phaseNum = 1;
|
|
5350
|
+
for (const milestone of milestones) {
|
|
5351
|
+
const milestoneMinutes = milestone.adjusted_minutes;
|
|
5352
|
+
if (currentMinutes + milestoneMinutes > sessionMinutes && currentMilestones.length > 0) {
|
|
5353
|
+
phases.push(buildPhase(currentMilestones, phaseNum));
|
|
5354
|
+
phaseNum++;
|
|
5355
|
+
currentMilestones = [];
|
|
5356
|
+
currentMinutes = 0;
|
|
5357
|
+
}
|
|
5358
|
+
currentMilestones.push(milestone);
|
|
5359
|
+
currentMinutes += milestoneMinutes;
|
|
4947
5360
|
}
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
ci += 1;
|
|
4951
|
-
const ids = strs(cat.concept_ids).filter((id) => nodes.some((n) => n.id === id));
|
|
4952
|
-
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, "") });
|
|
5361
|
+
if (currentMilestones.length > 0) {
|
|
5362
|
+
phases.push(buildPhase(currentMilestones, phaseNum));
|
|
4953
5363
|
}
|
|
4954
|
-
|
|
4955
|
-
return validateCurriculumFeed({
|
|
4956
|
-
schema_version: 2,
|
|
4957
|
-
source: { graph_type: "knowledge_graph", warnings: opts.warnings ?? [], hallucination_count: opts.hallucinationCount ?? 0 },
|
|
4958
|
-
learning_nodes: nodes,
|
|
4959
|
-
dependency_edges: edges,
|
|
4960
|
-
suggested_groupings: groupings,
|
|
4961
|
-
phases: []
|
|
4962
|
-
});
|
|
5364
|
+
return phases;
|
|
4963
5365
|
}
|
|
4964
|
-
function
|
|
4965
|
-
const
|
|
4966
|
-
const
|
|
4967
|
-
const
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
return validateCurriculumFeed({
|
|
4977
|
-
schema_version: 2,
|
|
4978
|
-
source: { graph_type: "project_graph", warnings: opts.warnings ?? [], hallucination_count: opts.hallucinationCount ?? 0 },
|
|
4979
|
-
learning_nodes: nodes,
|
|
4980
|
-
dependency_edges: edges,
|
|
4981
|
-
suggested_groupings: groupings,
|
|
4982
|
-
phases: []
|
|
4983
|
-
});
|
|
5366
|
+
function buildPhase(milestones, phaseNum) {
|
|
5367
|
+
const totalMinutes = milestones.reduce((sum, m) => sum + m.adjusted_minutes, 0);
|
|
5368
|
+
const depths = milestones.map((m) => m.depth);
|
|
5369
|
+
const uniqueDepths = [...new Set(depths)];
|
|
5370
|
+
const progression = uniqueDepths.map((d) => d.toUpperCase()).join(" \u2192 ");
|
|
5371
|
+
return {
|
|
5372
|
+
phase_id: phaseNum,
|
|
5373
|
+
phase_name: `Phase ${phaseNum}`,
|
|
5374
|
+
milestones,
|
|
5375
|
+
total_minutes: totalMinutes,
|
|
5376
|
+
concept_progression: progression
|
|
5377
|
+
};
|
|
4984
5378
|
}
|
|
4985
|
-
function
|
|
4986
|
-
const
|
|
4987
|
-
|
|
4988
|
-
|
|
5379
|
+
function assembleRoadmap(options) {
|
|
5380
|
+
const {
|
|
5381
|
+
projectGraph,
|
|
5382
|
+
featureConcepts = /* @__PURE__ */ new Map(),
|
|
5383
|
+
sessionMinutes = 90,
|
|
5384
|
+
overheadFactor = 1.15,
|
|
5385
|
+
maxMilestoneMinutes = 120
|
|
5386
|
+
} = options;
|
|
5387
|
+
const features = projectGraph.features || [];
|
|
5388
|
+
const keywordMap = buildKeywordMap(featureConcepts);
|
|
5389
|
+
const allMilestones = [];
|
|
5390
|
+
for (const feature of features) {
|
|
5391
|
+
const fid = feature.id || "F_UNK";
|
|
5392
|
+
const fConcepts = featureConcepts.get(fid) || [];
|
|
5393
|
+
const milestones = splitFeatureIntoMilestones(
|
|
5394
|
+
feature,
|
|
5395
|
+
fConcepts,
|
|
5396
|
+
maxMilestoneMinutes,
|
|
5397
|
+
overheadFactor
|
|
5398
|
+
);
|
|
5399
|
+
allMilestones.push(...milestones);
|
|
4989
5400
|
}
|
|
4990
|
-
|
|
4991
|
-
|
|
5401
|
+
for (const milestone of allMilestones) {
|
|
5402
|
+
milestone.adjusted_minutes = calculateOverhead(milestone.estimated_minutes, overheadFactor);
|
|
4992
5403
|
}
|
|
4993
|
-
|
|
4994
|
-
|
|
5404
|
+
attachKeywordsToMilestones(allMilestones, keywordMap);
|
|
5405
|
+
const phases = groupIntoPhases(allMilestones, sessionMinutes);
|
|
5406
|
+
const totalEstimatedMinutes = allMilestones.reduce((sum, m) => sum + m.estimated_minutes, 0);
|
|
5407
|
+
const totalAdjustedMinutes = allMilestones.reduce((sum, m) => sum + m.adjusted_minutes, 0);
|
|
5408
|
+
return {
|
|
5409
|
+
project: projectGraph.project,
|
|
5410
|
+
product: projectGraph.product,
|
|
5411
|
+
phases,
|
|
5412
|
+
feature_concepts: featureConcepts,
|
|
5413
|
+
time_budget: {
|
|
5414
|
+
session_minutes: sessionMinutes,
|
|
5415
|
+
overhead_factor: overheadFactor,
|
|
5416
|
+
total_estimated_minutes: totalEstimatedMinutes,
|
|
5417
|
+
total_adjusted_minutes: totalAdjustedMinutes
|
|
5418
|
+
}
|
|
5419
|
+
};
|
|
4995
5420
|
}
|
|
4996
5421
|
|
|
5422
|
+
// src/feed/index.ts
|
|
5423
|
+
init_curriculumFeedEmitter();
|
|
5424
|
+
|
|
4997
5425
|
// src/extractors/fileSelector.ts
|
|
4998
5426
|
async function selectFilesWithLlm(filePaths, goal, techStack, llmConfig) {
|
|
4999
5427
|
if (filePaths.length <= 15) {
|
|
@@ -5427,6 +5855,174 @@ async function runProjectGraphPipeline(options) {
|
|
|
5427
5855
|
};
|
|
5428
5856
|
}
|
|
5429
5857
|
|
|
5858
|
+
// src/pipeline/knowledgeGraphPipeline.ts
|
|
5859
|
+
init_curriculumFeedEmitter();
|
|
5860
|
+
async function runKnowledgeGraphPipeline(options) {
|
|
5861
|
+
const warnings = [];
|
|
5862
|
+
let parsedSyllabus = options.parsedSyllabus;
|
|
5863
|
+
if (!parsedSyllabus && options.syllabusText) {
|
|
5864
|
+
parsedSyllabus = parseSyllabus(options.syllabusText);
|
|
5865
|
+
}
|
|
5866
|
+
const effectiveSubject = options.subject || parsedSyllabus?.subject.title || "Untitled Subject";
|
|
5867
|
+
const effectiveDomain = options.domain || parsedSyllabus?.subject.domain || "other";
|
|
5868
|
+
const effectiveGradeBand = options.gradeBand || parsedSyllabus?.subject.gradeBand;
|
|
5869
|
+
const effectiveDesc = options.description || parsedSyllabus?.subject.description;
|
|
5870
|
+
const result = await generateKnowledgeGraph({
|
|
5871
|
+
...options,
|
|
5872
|
+
subject: effectiveSubject,
|
|
5873
|
+
domain: effectiveDomain,
|
|
5874
|
+
gradeBand: effectiveGradeBand,
|
|
5875
|
+
description: effectiveDesc,
|
|
5876
|
+
parsedSyllabus
|
|
5877
|
+
});
|
|
5878
|
+
warnings.push(...result.warnings);
|
|
5879
|
+
const curriculumFeed = emitCurriculumFeed(result.knowledgeGraph, {
|
|
5880
|
+
warnings
|
|
5881
|
+
});
|
|
5882
|
+
return {
|
|
5883
|
+
knowledgeGraph: result.knowledgeGraph,
|
|
5884
|
+
parsedSyllabus,
|
|
5885
|
+
curriculumFeed,
|
|
5886
|
+
verificationReport: result.verificationReport,
|
|
5887
|
+
warnings
|
|
5888
|
+
};
|
|
5889
|
+
}
|
|
5890
|
+
|
|
5891
|
+
// src/pipeline/hybridGraphPipeline.ts
|
|
5892
|
+
init_curriculumFeedEmitter();
|
|
5893
|
+
async function runHybridGraphPipeline(options) {
|
|
5894
|
+
const warnings = [];
|
|
5895
|
+
const result = await generateHybridGraph(options);
|
|
5896
|
+
warnings.push(...result.warnings);
|
|
5897
|
+
const curriculumFeed = emitCurriculumFeed(result.hybridGraph, {
|
|
5898
|
+
warnings
|
|
5899
|
+
});
|
|
5900
|
+
return {
|
|
5901
|
+
hybridGraph: result.hybridGraph,
|
|
5902
|
+
curriculumFeed,
|
|
5903
|
+
warnings
|
|
5904
|
+
};
|
|
5905
|
+
}
|
|
5906
|
+
|
|
5907
|
+
// src/pipeline/domainRouter.ts
|
|
5908
|
+
async function runDomainPipeline(options) {
|
|
5909
|
+
const warnings = [];
|
|
5910
|
+
const log = (step, msg) => {
|
|
5911
|
+
console.log(`[ROUTER-${step}] ${msg}`);
|
|
5912
|
+
options.onProgress?.(step, msg);
|
|
5913
|
+
};
|
|
5914
|
+
log("LAYER_1", "Detecting domain profile...");
|
|
5915
|
+
const domainProfile = detectDomainProfile(options);
|
|
5916
|
+
const effectiveMode = options.forcedLearningMode || domainProfile.learningMode;
|
|
5917
|
+
log("LAYER_2", `Routing to pipeline for learning mode: ${effectiveMode} (graphType: ${domainProfile.graphType})`);
|
|
5918
|
+
if (effectiveMode === "concept") {
|
|
5919
|
+
log("LAYER_3", "Executing Knowledge Graph Pipeline (Concept-driven)...");
|
|
5920
|
+
const knowledgeResult = await runKnowledgeGraphPipeline({
|
|
5921
|
+
subject: options.subject || domainProfile.detectedSubject || options.goal || options.description || "General Subject",
|
|
5922
|
+
description: options.description,
|
|
5923
|
+
syllabusText: options.syllabusText,
|
|
5924
|
+
parsedSyllabus: options.parsedSyllabus,
|
|
5925
|
+
gradeBand: options.gradeBand,
|
|
5926
|
+
domain: domainProfile.domainCategory,
|
|
5927
|
+
techStack: options.techStack || domainProfile.detectedTechStack?.[0],
|
|
5928
|
+
llmConfig: options.llmConfig,
|
|
5929
|
+
onProgress: options.onProgress,
|
|
5930
|
+
knowledgeTreePath: options.knowledgeTreePath,
|
|
5931
|
+
proposalsPath: options.proposalsPath
|
|
5932
|
+
});
|
|
5933
|
+
warnings.push(...knowledgeResult.warnings);
|
|
5934
|
+
return {
|
|
5935
|
+
domainProfile,
|
|
5936
|
+
graphType: "knowledge_graph",
|
|
5937
|
+
curriculumFeed: knowledgeResult.curriculumFeed,
|
|
5938
|
+
knowledgeResult,
|
|
5939
|
+
warnings
|
|
5940
|
+
};
|
|
5941
|
+
}
|
|
5942
|
+
if (effectiveMode === "hybrid") {
|
|
5943
|
+
log("LAYER_3", "Executing Hybrid Graph Pipeline...");
|
|
5944
|
+
if (options.repoDir) {
|
|
5945
|
+
const projectResult2 = await runProjectGraphPipeline({
|
|
5946
|
+
repoDir: options.repoDir,
|
|
5947
|
+
goal: options.goal || "Build complete project",
|
|
5948
|
+
techStack: options.techStack || "typescript",
|
|
5949
|
+
llmConfig: options.llmConfig,
|
|
5950
|
+
embeddings: options.embeddings,
|
|
5951
|
+
onProgress: options.onProgress
|
|
5952
|
+
});
|
|
5953
|
+
const knowledgeResult = await runKnowledgeGraphPipeline({
|
|
5954
|
+
subject: options.subject || domainProfile.detectedSubject || options.goal || "Subject Theory",
|
|
5955
|
+
description: options.description,
|
|
5956
|
+
syllabusText: options.syllabusText,
|
|
5957
|
+
parsedSyllabus: options.parsedSyllabus,
|
|
5958
|
+
domain: domainProfile.domainCategory,
|
|
5959
|
+
techStack: options.techStack,
|
|
5960
|
+
llmConfig: options.llmConfig,
|
|
5961
|
+
onProgress: options.onProgress
|
|
5962
|
+
});
|
|
5963
|
+
const hybridResult = await runHybridGraphPipeline({
|
|
5964
|
+
projectGraph: projectResult2.projectGraph,
|
|
5965
|
+
knowledgeGraph: knowledgeResult.knowledgeGraph,
|
|
5966
|
+
llmConfig: options.llmConfig,
|
|
5967
|
+
onProgress: options.onProgress
|
|
5968
|
+
});
|
|
5969
|
+
warnings.push(...hybridResult.warnings);
|
|
5970
|
+
return {
|
|
5971
|
+
domainProfile,
|
|
5972
|
+
graphType: "hybrid_graph",
|
|
5973
|
+
curriculumFeed: hybridResult.curriculumFeed,
|
|
5974
|
+
projectResult: projectResult2,
|
|
5975
|
+
knowledgeResult,
|
|
5976
|
+
hybridResult,
|
|
5977
|
+
warnings
|
|
5978
|
+
};
|
|
5979
|
+
} else {
|
|
5980
|
+
warnings.push("Hybrid learning mode requested but repoDir was not provided \u2014 falling back to concept-driven knowledge graph pipeline");
|
|
5981
|
+
const knowledgeResult = await runKnowledgeGraphPipeline({
|
|
5982
|
+
subject: options.subject || domainProfile.detectedSubject || options.goal || "Subject Theory",
|
|
5983
|
+
description: options.description,
|
|
5984
|
+
syllabusText: options.syllabusText,
|
|
5985
|
+
parsedSyllabus: options.parsedSyllabus,
|
|
5986
|
+
domain: domainProfile.domainCategory,
|
|
5987
|
+
techStack: options.techStack,
|
|
5988
|
+
llmConfig: options.llmConfig,
|
|
5989
|
+
onProgress: options.onProgress
|
|
5990
|
+
});
|
|
5991
|
+
warnings.push(...knowledgeResult.warnings);
|
|
5992
|
+
return {
|
|
5993
|
+
domainProfile,
|
|
5994
|
+
graphType: "knowledge_graph",
|
|
5995
|
+
curriculumFeed: knowledgeResult.curriculumFeed,
|
|
5996
|
+
knowledgeResult,
|
|
5997
|
+
warnings
|
|
5998
|
+
};
|
|
5999
|
+
}
|
|
6000
|
+
}
|
|
6001
|
+
log("LAYER_3", "Executing Project Graph Pipeline (Product-driven)...");
|
|
6002
|
+
if (!options.repoDir) {
|
|
6003
|
+
throw new Error("repoDir is required for product-driven project graph pipeline");
|
|
6004
|
+
}
|
|
6005
|
+
const projectResult = await runProjectGraphPipeline({
|
|
6006
|
+
repoDir: options.repoDir,
|
|
6007
|
+
goal: options.goal || "Build complete project",
|
|
6008
|
+
techStack: options.techStack || domainProfile.detectedTechStack?.[0] || "typescript",
|
|
6009
|
+
llmConfig: options.llmConfig,
|
|
6010
|
+
embeddings: options.embeddings,
|
|
6011
|
+
onProgress: options.onProgress
|
|
6012
|
+
});
|
|
6013
|
+
const { emitCurriculumFeed: emitCurriculumFeed2 } = await Promise.resolve().then(() => (init_curriculumFeedEmitter(), curriculumFeedEmitter_exports));
|
|
6014
|
+
const curriculumFeed = emitCurriculumFeed2(projectResult.projectGraph, {
|
|
6015
|
+
warnings
|
|
6016
|
+
});
|
|
6017
|
+
return {
|
|
6018
|
+
domainProfile,
|
|
6019
|
+
graphType: "project_graph",
|
|
6020
|
+
curriculumFeed,
|
|
6021
|
+
projectResult,
|
|
6022
|
+
warnings
|
|
6023
|
+
};
|
|
6024
|
+
}
|
|
6025
|
+
|
|
5430
6026
|
exports.ArchEdgeSchema = ArchEdgeSchema;
|
|
5431
6027
|
exports.ArchNodeSchema = ArchNodeSchema;
|
|
5432
6028
|
exports.ArchitectureDetailSchema = ArchitectureDetailSchema;
|
|
@@ -5436,7 +6032,6 @@ exports.CapabilitySchema = CapabilitySchema;
|
|
|
5436
6032
|
exports.ConceptCategorySchema = ConceptCategorySchema;
|
|
5437
6033
|
exports.ConceptIntroductionSchema = ConceptIntroductionSchema;
|
|
5438
6034
|
exports.ConceptSchema = ConceptSchema;
|
|
5439
|
-
exports.CurriculumFeedSchema = CurriculumFeedSchema;
|
|
5440
6035
|
exports.DataIntegrationSchema = DataIntegrationSchema;
|
|
5441
6036
|
exports.DecompositionSchema = DecompositionSchema;
|
|
5442
6037
|
exports.DetectionConfidenceSchema = DetectionConfidenceSchema;
|
|
@@ -5446,13 +6041,9 @@ exports.DomainCategorySchema = DomainCategorySchema;
|
|
|
5446
6041
|
exports.DomainProfileSchema = DomainProfileSchema;
|
|
5447
6042
|
exports.ExperienceComponentSchema = ExperienceComponentSchema;
|
|
5448
6043
|
exports.ExperienceSchema = ExperienceSchema;
|
|
6044
|
+
exports.ExperimentProcedureSchema = ExperimentProcedureSchema;
|
|
5449
6045
|
exports.FeatureSchema = FeatureSchema;
|
|
5450
|
-
exports.
|
|
5451
|
-
exports.FeedGroupingSchema = FeedGroupingSchema;
|
|
5452
|
-
exports.FeedKeywordsSchema = FeedKeywordsSchema;
|
|
5453
|
-
exports.FeedNodeSchema = FeedNodeSchema;
|
|
5454
|
-
exports.FeedPhaseSchema = FeedPhaseSchema;
|
|
5455
|
-
exports.FeedScaffoldCandidateSchema = FeedScaffoldCandidateSchema;
|
|
6046
|
+
exports.GrammarRuleSchema = GrammarRuleSchema;
|
|
5456
6047
|
exports.GraphTypeSchema = GraphTypeSchema;
|
|
5457
6048
|
exports.HardwareFeatureExtensionSchema = HardwareFeatureExtensionSchema;
|
|
5458
6049
|
exports.HardwareStepSchema = HardwareStepSchema;
|
|
@@ -5465,8 +6056,11 @@ exports.JourneySchema = JourneySchema;
|
|
|
5465
6056
|
exports.JourneyStepSchema = JourneyStepSchema;
|
|
5466
6057
|
exports.KnowledgeGraphSchema = KnowledgeGraphSchema;
|
|
5467
6058
|
exports.LEARNER_CLAUSE = LEARNER_CLAUSE;
|
|
6059
|
+
exports.LanguageConceptExtensionSchema = LanguageConceptExtensionSchema;
|
|
5468
6060
|
exports.LearningModeSchema = LearningModeSchema;
|
|
5469
6061
|
exports.LearningPathStepSchema = LearningPathStepSchema;
|
|
6062
|
+
exports.MathConceptExtensionSchema = MathConceptExtensionSchema;
|
|
6063
|
+
exports.MathTheoremSchema = MathTheoremSchema;
|
|
5470
6064
|
exports.MilestoneDecompositionSchema = MilestoneDecompositionSchema;
|
|
5471
6065
|
exports.MissingGapSchema = MissingGapSchema;
|
|
5472
6066
|
exports.NavigationSchema = NavigationSchema;
|
|
@@ -5479,6 +6073,7 @@ exports.ProjectGraphSchema = ProjectGraphSchema;
|
|
|
5479
6073
|
exports.ProjectInfoSchema = ProjectInfoSchema;
|
|
5480
6074
|
exports.RequirementSchema = RequirementSchema;
|
|
5481
6075
|
exports.SIO_CODE_RE = SIO_CODE_RE;
|
|
6076
|
+
exports.ScienceConceptExtensionSchema = ScienceConceptExtensionSchema;
|
|
5482
6077
|
exports.ScreenSchema = ScreenSchema;
|
|
5483
6078
|
exports.ShapeSchema = ShapeSchema;
|
|
5484
6079
|
exports.StepEffortSchema = StepEffortSchema;
|
|
@@ -5499,6 +6094,8 @@ exports.WiringSchema = WiringSchema;
|
|
|
5499
6094
|
exports.assembleRoadmap = assembleRoadmap;
|
|
5500
6095
|
exports.assignConceptCodes = assignConceptCodes;
|
|
5501
6096
|
exports.auditDepthLayers = auditDepthLayers;
|
|
6097
|
+
exports.auditSyllabusCoverage = auditSyllabusCoverage;
|
|
6098
|
+
exports.breakPrerequisiteCycles = breakPrerequisiteCycles;
|
|
5502
6099
|
exports.cioActionSlug = cioActionSlug;
|
|
5503
6100
|
exports.cioCode = cioCode;
|
|
5504
6101
|
exports.conceptCodeFromName = conceptCodeFromName;
|
|
@@ -5506,6 +6103,7 @@ exports.detectDomain = detectDomain;
|
|
|
5506
6103
|
exports.detectDomainProfile = detectDomainProfile;
|
|
5507
6104
|
exports.detectInputType = detectInputType;
|
|
5508
6105
|
exports.detectLearningMode = detectLearningMode;
|
|
6106
|
+
exports.detectPrerequisiteCycles = detectPrerequisiteCycles;
|
|
5509
6107
|
exports.emitCurriculumFeed = emitCurriculumFeed;
|
|
5510
6108
|
exports.escalateAndMapConcepts = escalateAndMapConcepts;
|
|
5511
6109
|
exports.extractFeatureSteps = extractFeatureSteps;
|
|
@@ -5522,8 +6120,12 @@ exports.mergeTsResults = mergeTsResults;
|
|
|
5522
6120
|
exports.parseCppFile = parseCppFile;
|
|
5523
6121
|
exports.parsePythonFile = parsePythonFile;
|
|
5524
6122
|
exports.parseSwiftFile = parseSwiftFile;
|
|
6123
|
+
exports.parseSyllabus = parseSyllabus;
|
|
5525
6124
|
exports.parseTsFile = parseTsFile;
|
|
5526
6125
|
exports.resolveConcepts = resolveConcepts;
|
|
6126
|
+
exports.runDomainPipeline = runDomainPipeline;
|
|
6127
|
+
exports.runHybridGraphPipeline = runHybridGraphPipeline;
|
|
6128
|
+
exports.runKnowledgeGraphPipeline = runKnowledgeGraphPipeline;
|
|
5527
6129
|
exports.runProjectGraphPipeline = runProjectGraphPipeline;
|
|
5528
6130
|
exports.scoreByContent = scoreByContent;
|
|
5529
6131
|
exports.scoreByPath = scoreByPath;
|
|
@@ -5534,6 +6136,7 @@ exports.standardStatement = standardStatement;
|
|
|
5534
6136
|
exports.techTagFor = techTagFor;
|
|
5535
6137
|
exports.uloCode = uloCode;
|
|
5536
6138
|
exports.validateCurriculumFeed = validateCurriculumFeed;
|
|
6139
|
+
exports.verifyKnowledgeGraph = verifyKnowledgeGraph;
|
|
5537
6140
|
exports.verifyProjectGraph = verifyProjectGraph;
|
|
5538
6141
|
//# sourceMappingURL=index.cjs.map
|
|
5539
6142
|
//# sourceMappingURL=index.cjs.map
|