@thanh01.pmt/domain-kit 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +682 -168
- package/dist/assembly/index.d.cts +2 -2
- package/dist/assembly/index.d.ts +2 -2
- package/dist/{chunk-TYXSJUQU.mjs → chunk-2OGNQUXC.mjs} +81 -32
- package/dist/chunk-2OGNQUXC.mjs.map +1 -0
- package/dist/{chunk-G7HLFMD2.mjs → chunk-5OMSYKQP.mjs} +43 -23
- package/dist/chunk-5OMSYKQP.mjs.map +1 -0
- package/dist/{chunk-BL5NBKPH.mjs → chunk-67GXK3ZE.mjs} +18 -8
- package/dist/chunk-67GXK3ZE.mjs.map +1 -0
- package/dist/{chunk-E4GCCWJO.mjs → chunk-CGNXGVHV.mjs} +37 -4
- package/dist/chunk-CGNXGVHV.mjs.map +1 -0
- package/dist/{chunk-VCPIWJ7R.mjs → chunk-ERDTHXQA.mjs} +21 -3
- package/dist/chunk-ERDTHXQA.mjs.map +1 -0
- package/dist/chunk-OYSZQVPY.mjs +10 -0
- package/dist/chunk-OYSZQVPY.mjs.map +1 -0
- package/dist/{chunk-TWKIVUTQ.mjs → chunk-WKTH3JPA.mjs} +197 -36
- package/dist/chunk-WKTH3JPA.mjs.map +1 -0
- package/dist/{conceptEscalator-YfMKIKCZ.d.ts → conceptEscalator-BCsw0ys_.d.ts} +10 -1
- package/dist/{conceptEscalator-ltRLtPhf.d.cts → conceptEscalator-DPrs4PwC.d.cts} +10 -1
- package/dist/concepts/index.d.cts +1 -1
- package/dist/concepts/index.d.ts +1 -1
- package/dist/{curriculumFeedSchema-DUFwFyr2.d.ts → curriculumFeedSchema-DYayelAA.d.cts} +156 -1
- package/dist/{curriculumFeedSchema-DUFwFyr2.d.cts → curriculumFeedSchema-DYayelAA.d.ts} +156 -1
- package/dist/detector/index.cjs +21 -6
- package/dist/detector/index.cjs.map +1 -1
- package/dist/detector/index.mjs +2 -1
- package/dist/feed/index.cjs +229 -33
- package/dist/feed/index.cjs.map +1 -1
- package/dist/feed/index.d.cts +32 -2
- package/dist/feed/index.d.ts +32 -2
- package/dist/feed/index.mjs +2 -2
- package/dist/graph/index.cjs +73 -29
- package/dist/graph/index.cjs.map +1 -1
- package/dist/graph/index.d.cts +14 -5
- package/dist/graph/index.d.ts +14 -5
- package/dist/graph/index.mjs +2 -1
- package/dist/{hybridGraphPipeline-DXQDbBEj.d.ts → hybridGraphPipeline-9dRKJj7r.d.ts} +6 -2
- package/dist/{hybridGraphPipeline-dT-bio-3.d.cts → hybridGraphPipeline-CocfYe4d.d.cts} +6 -2
- package/dist/{hybridGraphSchema-nrUeSwpU.d.ts → hybridGraphSchema-C37qxdTM.d.cts} +16 -3
- package/dist/{hybridGraphSchema-nrUeSwpU.d.cts → hybridGraphSchema-C37qxdTM.d.ts} +16 -3
- package/dist/index.cjs +2634 -2359
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.mjs +7 -6
- package/dist/llmClient-CX5uUiQ1.d.cts +60 -0
- package/dist/llmClient-CX5uUiQ1.d.ts +60 -0
- package/dist/pipeline/index.cjs +2207 -1958
- package/dist/pipeline/index.cjs.map +1 -1
- package/dist/pipeline/index.d.cts +13 -5
- package/dist/pipeline/index.d.ts +13 -5
- package/dist/pipeline/index.mjs +6 -5
- package/dist/schemas/index.cjs +55 -2
- package/dist/schemas/index.cjs.map +1 -1
- package/dist/schemas/index.d.cts +2 -2
- package/dist/schemas/index.d.ts +2 -2
- package/dist/schemas/index.mjs +2 -2
- package/package.json +10 -9
- package/LICENSE +0 -21
- package/dist/chunk-BL5NBKPH.mjs.map +0 -1
- package/dist/chunk-E4GCCWJO.mjs.map +0 -1
- package/dist/chunk-G7HLFMD2.mjs.map +0 -1
- package/dist/chunk-TWKIVUTQ.mjs.map +0 -1
- package/dist/chunk-TYXSJUQU.mjs.map +0 -1
- package/dist/chunk-VCPIWJ7R.mjs.map +0 -1
- package/dist/curriculumFeedEmitter-T7NUBLAM.mjs +0 -4
- package/dist/curriculumFeedEmitter-T7NUBLAM.mjs.map +0 -1
- package/dist/llmClient-ysPhLjcH.d.cts +0 -16
- package/dist/llmClient-ysPhLjcH.d.ts +0 -16
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { u as ProjectGraph, c as Step } from '../projectGraphSchema-DnD7orZV.cjs';
|
|
2
|
-
import { C as ConceptMapping } from '../conceptEscalator-
|
|
2
|
+
import { C as ConceptMapping } from '../conceptEscalator-DPrs4PwC.cjs';
|
|
3
3
|
import 'zod';
|
|
4
|
-
import '../llmClient-
|
|
4
|
+
import '../llmClient-CX5uUiQ1.cjs';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Roadmap assembly — assembles final roadmap with keyword tracking and time awareness.
|
package/dist/assembly/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { u as ProjectGraph, c as Step } from '../projectGraphSchema-DnD7orZV.js';
|
|
2
|
-
import { C as ConceptMapping } from '../conceptEscalator-
|
|
2
|
+
import { C as ConceptMapping } from '../conceptEscalator-BCsw0ys_.js';
|
|
3
3
|
import 'zod';
|
|
4
|
-
import '../llmClient-
|
|
4
|
+
import '../llmClient-CX5uUiQ1.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Roadmap assembly — assembles final roadmap with keyword tracking and time awareness.
|
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
import { parseSyllabus } from './chunk-T4Y2DE2W.mjs';
|
|
2
|
+
import { stripDiacritics } from './chunk-OYSZQVPY.mjs';
|
|
2
3
|
import { readFileSync, mkdirSync, writeFileSync } from 'fs';
|
|
3
4
|
import { tmpdir } from 'os';
|
|
4
5
|
import { join } from 'path';
|
|
5
6
|
|
|
6
7
|
// src/utils/llmClient.ts
|
|
8
|
+
function summarizeProvenance(callLog) {
|
|
9
|
+
const providers = [];
|
|
10
|
+
const models = [];
|
|
11
|
+
let failed = 0;
|
|
12
|
+
for (const r of callLog) {
|
|
13
|
+
if (!providers.includes(r.provider)) providers.push(r.provider);
|
|
14
|
+
if (!models.includes(r.model)) models.push(r.model);
|
|
15
|
+
if (!r.ok) failed++;
|
|
16
|
+
}
|
|
17
|
+
return { providers, models, calls: callLog.length, failed_calls: failed, degraded: failed > 0 };
|
|
18
|
+
}
|
|
7
19
|
var LlmClientError = class extends Error {
|
|
8
20
|
constructor(message, status, cause) {
|
|
9
21
|
super(message);
|
|
@@ -36,9 +48,10 @@ function resolveProviderChain(config) {
|
|
|
36
48
|
return chain;
|
|
37
49
|
}
|
|
38
50
|
var MIN_REQUEST_INTERVAL_MS = 2500;
|
|
51
|
+
var requestIntervalMs = Number(process.env.LLM_MIN_REQUEST_INTERVAL_MS || MIN_REQUEST_INTERVAL_MS);
|
|
39
52
|
var lastRequestAt = 0;
|
|
40
53
|
async function pacedDelay() {
|
|
41
|
-
const wait = lastRequestAt +
|
|
54
|
+
const wait = lastRequestAt + requestIntervalMs - Date.now();
|
|
42
55
|
if (wait > 0) await new Promise((resolve) => setTimeout(resolve, wait));
|
|
43
56
|
lastRequestAt = Date.now();
|
|
44
57
|
}
|
|
@@ -53,6 +66,10 @@ function createLlmClient(config) {
|
|
|
53
66
|
primary.apiKey;
|
|
54
67
|
primary.baseUrl;
|
|
55
68
|
const model = primary.model;
|
|
69
|
+
if (config?.minRequestIntervalMs !== void 0) {
|
|
70
|
+
requestIntervalMs = Math.max(0, config.minRequestIntervalMs);
|
|
71
|
+
}
|
|
72
|
+
const callLog = [];
|
|
56
73
|
async function chat(messages, options) {
|
|
57
74
|
const temperature = options?.temperature ?? config?.temperature ?? 0.1;
|
|
58
75
|
const maxTokens = options?.maxTokens ?? config?.maxTokens ?? parseInt(process.env.LLM_MAX_TOKENS || "65536", 10);
|
|
@@ -67,6 +84,7 @@ function createLlmClient(config) {
|
|
|
67
84
|
await new Promise((resolve) => setTimeout(resolve, 2e4));
|
|
68
85
|
}
|
|
69
86
|
for (const provider of chain) {
|
|
87
|
+
const recIdx = callLog.push({ provider: provider.label, model: provider.model, ok: false }) - 1;
|
|
70
88
|
try {
|
|
71
89
|
const headers = {
|
|
72
90
|
"Content-Type": "application/json",
|
|
@@ -127,6 +145,7 @@ function createLlmClient(config) {
|
|
|
127
145
|
const retryData = await retry.json();
|
|
128
146
|
const retryContent2 = retryData.choices?.[0]?.message?.content || "";
|
|
129
147
|
if (!retryContent2.trim()) throw err;
|
|
148
|
+
callLog[recIdx] = { ...callLog[recIdx], ok: true };
|
|
130
149
|
return {
|
|
131
150
|
content: retryContent2,
|
|
132
151
|
model: retryData.model || provider.model,
|
|
@@ -155,6 +174,7 @@ function createLlmClient(config) {
|
|
|
155
174
|
if (!retry.ok) throw err;
|
|
156
175
|
const retryData = await retry.json();
|
|
157
176
|
const retryContent = retryData.choices?.[0]?.message?.content || "";
|
|
177
|
+
callLog[recIdx] = { ...callLog[recIdx], ok: true };
|
|
158
178
|
return {
|
|
159
179
|
content: retryContent,
|
|
160
180
|
model: retryData.model || provider.model,
|
|
@@ -185,6 +205,7 @@ function createLlmClient(config) {
|
|
|
185
205
|
};
|
|
186
206
|
} catch (err) {
|
|
187
207
|
const msg = err instanceof Error ? err.message : String(err);
|
|
208
|
+
callLog[recIdx] = { ...callLog[recIdx], ok: false, error: msg.slice(0, 200) };
|
|
188
209
|
attempts.push(`${provider.label}: ${msg}`);
|
|
189
210
|
lastError = err instanceof LlmClientError ? err : new LlmClientError(`[${provider.label}] ${msg}`);
|
|
190
211
|
}
|
|
@@ -194,7 +215,7 @@ function createLlmClient(config) {
|
|
|
194
215
|
}
|
|
195
216
|
throw lastError ?? new LlmClientError("All LLM providers failed after " + MAX_CHAIN_ROUNDS + " rounds.");
|
|
196
217
|
}
|
|
197
|
-
return { chat, model };
|
|
218
|
+
return { chat, model, callLog };
|
|
198
219
|
}
|
|
199
220
|
function parseJsonFromLlm(text) {
|
|
200
221
|
let cleaned = text.trim();
|
|
@@ -258,8 +279,8 @@ async function llmChatJson(client, systemPrompt, userPrompt, options) {
|
|
|
258
279
|
|
|
259
280
|
// src/graph/scaffoldExtractor.ts
|
|
260
281
|
async function extractScaffold(options) {
|
|
261
|
-
const { goal, techStack, fileList, llmConfig } = options;
|
|
262
|
-
const client = createLlmClient(llmConfig);
|
|
282
|
+
const { goal, techStack, fileList, llmConfig, sharedClient } = options;
|
|
283
|
+
const client = sharedClient ?? createLlmClient(llmConfig);
|
|
263
284
|
const fileListStr = fileList.length > 0 ? fileList.join("\n") : "(empty)";
|
|
264
285
|
const systemPrompt = "You are a programming pedagogy expert. For ONE concrete project, extract the 'FOUNDATION & SETUP' feature (id F0) \u2014 the COMPLETE set of 'MUST-KNOW' steps the learner needs before studying the first feature. Break it into concrete ACTION steps (5-8 steps), each with completion_level='base'. Include:\n1. Tools required for this tech stack (IDE, simulator/emulator, terminal, git, package manager) + basic operations (open project, build, run, debug, commit).\n2. Create the initial project: from a template OR clone/open the base-project (the provided repo).\n3. MINIMAL programming knowledge of the language (variables, types, functions, if/for, view declaration) + build one small demo (e.g. a single-screen Hello World app) BEFORE touching the real project.\n4. Repo map of the actual project: folder structure, entry point, how to build/run, main architecture (MVVM/Flux...), important packages/modules.\n5. Development loop (build->run->see result->debug), how to read/fix basic compile errors, minimal git workflow (clone/branch/commit/push).\nkeywords[]: real tool/language terms (e.g. 'Xcode', 'Simulator', 'Git', 'Swift', '@main', 'MVVM'). files[]: leave [] or use real paths if the step touches specific files. description: ONE concise sentence, MAX 140 characters. intent: WHY this step matters. outcome: {user_visible: string, technical: string}. acceptance[]: 2 verifiable criteria. effort: {estimated_minutes: number, complexity: low|medium|high}. NEVER invent. ALL text must be in ENGLISH (technical terms stay as-is). Return JSON containing only feature F0.";
|
|
265
286
|
const userPrompt = `App goal: ${goal}
|
|
@@ -344,8 +365,8 @@ ${content}`);
|
|
|
344
365
|
return blocks.join("\n\n");
|
|
345
366
|
}
|
|
346
367
|
async function extractProjectOverview(options) {
|
|
347
|
-
const { goal, techStack, sdkApiIndex, fileContentsMap, astKeywords, llmConfig } = options;
|
|
348
|
-
const client = createLlmClient(llmConfig);
|
|
368
|
+
const { goal, techStack, sdkApiIndex, fileContentsMap, astKeywords, llmConfig, sharedClient } = options;
|
|
369
|
+
const client = sharedClient ?? createLlmClient(llmConfig);
|
|
349
370
|
const usedSdkApis = sdkApiIndex.sdk_apis.filter((api) => api.used_in_demo).map((api) => api.name).slice(0, 2e3);
|
|
350
371
|
const allFilesStr = buildSourceText(fileContentsMap);
|
|
351
372
|
let keywordsAnchor = "";
|
|
@@ -414,8 +435,8 @@ ${content}`);
|
|
|
414
435
|
return blocks.join("\n\n");
|
|
415
436
|
}
|
|
416
437
|
async function extractFeatureSteps(options) {
|
|
417
|
-
const { goal, techStack, sdkApiIndex, fileContentsMap, feature, llmConfig } = options;
|
|
418
|
-
const client = createLlmClient(llmConfig);
|
|
438
|
+
const { goal, techStack, sdkApiIndex, fileContentsMap, feature, llmConfig, sharedClient } = options;
|
|
439
|
+
const client = sharedClient ?? createLlmClient(llmConfig);
|
|
419
440
|
const usedSdkApis = sdkApiIndex.sdk_apis.filter((api) => api.used_in_demo).map((api) => api.name).slice(0, 2e3);
|
|
420
441
|
const allFilesStr = buildSourceText2(fileContentsMap);
|
|
421
442
|
const fid = feature.id || "F_UNK";
|
|
@@ -482,8 +503,8 @@ Split this feature into 2-6 action steps. Return JSON:
|
|
|
482
503
|
concept_codes: []
|
|
483
504
|
}));
|
|
484
505
|
}
|
|
485
|
-
async function extractFeatureStepsBatched(goal, techStack, sdkApiIndex, fileContentsMap, featuresMeta, llmConfig) {
|
|
486
|
-
const client = createLlmClient(llmConfig);
|
|
506
|
+
async function extractFeatureStepsBatched(goal, techStack, sdkApiIndex, fileContentsMap, featuresMeta, llmConfig, sharedClient) {
|
|
507
|
+
const client = sharedClient ?? createLlmClient(llmConfig);
|
|
487
508
|
const usedSdkApis = sdkApiIndex.sdk_apis.filter((api) => api.used_in_demo).map((api) => api.name).slice(0, 2e3);
|
|
488
509
|
const sourceText = buildSourceText2(fileContentsMap);
|
|
489
510
|
const featuresListing = featuresMeta.map(
|
|
@@ -750,7 +771,7 @@ function inferDepthFromContext(keyword, fileContentsMap) {
|
|
|
750
771
|
return { depth: "sio", rationale: `Default: treating '${keyword}' as SIO (implementation-level)` };
|
|
751
772
|
}
|
|
752
773
|
async function escalateAndMapConcepts(options) {
|
|
753
|
-
const { projectGraph, fileContentsMap, availableConcepts, conceptMapOverride, llmConfig } = options;
|
|
774
|
+
const { projectGraph, fileContentsMap, availableConcepts, conceptMapOverride, llmConfig, sharedClient } = options;
|
|
754
775
|
const features = projectGraph.features || [];
|
|
755
776
|
const allTerms = /* @__PURE__ */ new Set();
|
|
756
777
|
for (const feature of features) {
|
|
@@ -766,7 +787,7 @@ async function escalateAndMapConcepts(options) {
|
|
|
766
787
|
llmResults = conceptMapOverride;
|
|
767
788
|
} else {
|
|
768
789
|
try {
|
|
769
|
-
const client = createLlmClient(llmConfig);
|
|
790
|
+
const client = sharedClient ?? createLlmClient(llmConfig);
|
|
770
791
|
const conceptBank = (availableConcepts || []).sort().join(", ") || "(empty)";
|
|
771
792
|
const termEntries = termList.map((term) => {
|
|
772
793
|
const snippets = extractTermSnippets(term, fileContentsMap);
|
|
@@ -819,13 +840,16 @@ Return JSON: {"results": {"<keyword>": {"concept_code": "...", "concept_name": "
|
|
|
819
840
|
const cName = info.concept_name || kw;
|
|
820
841
|
let depth = "sio";
|
|
821
842
|
let depthRationale = "Default: SIO";
|
|
843
|
+
let depthSource = "override";
|
|
822
844
|
if (info.depth && ["ulo", "cio", "sio"].includes(info.depth)) {
|
|
823
845
|
depth = info.depth;
|
|
824
846
|
depthRationale = `LLM classified as ${depth}`;
|
|
847
|
+
depthSource = "llm";
|
|
825
848
|
} else {
|
|
826
849
|
const inferred = inferDepthFromContext(kw, fileContentsMap);
|
|
827
850
|
depth = inferred.depth;
|
|
828
851
|
depthRationale = inferred.rationale;
|
|
852
|
+
depthSource = "heuristic";
|
|
829
853
|
}
|
|
830
854
|
const kwKey = `${cCode}::${kw}`;
|
|
831
855
|
if (seen.has(kwKey)) continue;
|
|
@@ -843,6 +867,7 @@ Return JSON: {"results": {"<keyword>": {"concept_code": "...", "concept_name": "
|
|
|
843
867
|
keyword: kw,
|
|
844
868
|
depth,
|
|
845
869
|
depth_rationale: depthRationale,
|
|
870
|
+
depth_source: depthSource,
|
|
846
871
|
evidence_files: evidenceFiles
|
|
847
872
|
});
|
|
848
873
|
}
|
|
@@ -1545,7 +1570,7 @@ function loadKnowledgeTreeCatalog(tsvPath) {
|
|
|
1545
1570
|
}
|
|
1546
1571
|
function tokens(s) {
|
|
1547
1572
|
return new Set(
|
|
1548
|
-
s
|
|
1573
|
+
stripDiacritics(s).replace(/[^a-z0-9\s-]/g, " ").split(/\s+/).filter((w) => w.length >= 4)
|
|
1549
1574
|
);
|
|
1550
1575
|
}
|
|
1551
1576
|
function filterBySelection(catalog, selectedCategories, selectedTopics, analysisConcepts, maxConcepts = 120) {
|
|
@@ -1719,6 +1744,15 @@ async function standardizeConcepts(opts) {
|
|
|
1719
1744
|
}
|
|
1720
1745
|
|
|
1721
1746
|
// src/graph/knowledgeGraphPipeline.ts
|
|
1747
|
+
var MIN_CALIBRATION_MINUTES = 15;
|
|
1748
|
+
var MAX_CALIBRATION_MINUTES = 60;
|
|
1749
|
+
var DEFAULT_CONCEPT_MINUTES = 30;
|
|
1750
|
+
var MIN_CALIBRATION_DIVERGENCE = 0.5;
|
|
1751
|
+
function calibrateConceptMinutes(sioCount, problemTypeCount, prereqCount, hasHighBloom) {
|
|
1752
|
+
const ratio = (value, cap) => Math.min(1, Math.max(0, value) / cap);
|
|
1753
|
+
const score = 0.3 * ratio(sioCount, 4) + 0.25 * ratio(problemTypeCount, 4) + 0.2 * ratio(prereqCount, 4) + 0.25 * (hasHighBloom ? 1 : 0);
|
|
1754
|
+
return Math.round(MIN_CALIBRATION_MINUTES + score * (MAX_CALIBRATION_MINUTES - MIN_CALIBRATION_MINUTES));
|
|
1755
|
+
}
|
|
1722
1756
|
function topologicalSort(concepts) {
|
|
1723
1757
|
const inDegree = /* @__PURE__ */ new Map();
|
|
1724
1758
|
const adjacency = /* @__PURE__ */ new Map();
|
|
@@ -1884,7 +1918,7 @@ Return JSON:
|
|
|
1884
1918
|
}
|
|
1885
1919
|
{
|
|
1886
1920
|
const STOP = /* @__PURE__ */ new Set(["and", "the", "for", "with", "from", "that", "this", "into", "when", "while", "using", "their", "your", "each", "how", "why", "what", "between", "through", "about"]);
|
|
1887
|
-
const tokensFrom = (text) => text
|
|
1921
|
+
const tokensFrom = (text) => stripDiacritics(text).replace(/[^a-z0-9\s-]/g, " ").split(/\s+/).filter((w) => w.length >= 4 && !STOP.has(w));
|
|
1888
1922
|
for (const c of concepts) {
|
|
1889
1923
|
const existing = (c.keywords || []).map((k) => String(k).trim()).filter(Boolean);
|
|
1890
1924
|
if (existing.length === 0) {
|
|
@@ -2052,14 +2086,11 @@ Return JSON:
|
|
|
2052
2086
|
const hasHighBloom = (concept.problem_types || []).some(
|
|
2053
2087
|
(pt) => ["Analyze", "Evaluate", "Create"].includes(pt.bloom_level || "")
|
|
2054
2088
|
);
|
|
2055
|
-
const
|
|
2056
|
-
const
|
|
2057
|
-
|
|
2058
|
-
if (llmEstimate === 30 || Math.abs(calibrated - llmEstimate) / llmEstimate > 0.5) {
|
|
2089
|
+
const calibrated = calibrateConceptMinutes(sioCount, ptCount, prereqCount, hasHighBloom);
|
|
2090
|
+
const llmEstimate = concept.estimated_minutes || DEFAULT_CONCEPT_MINUTES;
|
|
2091
|
+
if (Math.abs(calibrated - llmEstimate) / llmEstimate > MIN_CALIBRATION_DIVERGENCE) {
|
|
2059
2092
|
concept.estimated_minutes = calibrated;
|
|
2060
|
-
|
|
2061
|
-
warnings.push("Concept " + concept.id + ": minutes calibrated " + llmEstimate + "m \u2192 " + calibrated + "m (score=" + score.toFixed(2) + ")");
|
|
2062
|
-
}
|
|
2093
|
+
warnings.push("Concept " + concept.id + ": minutes calibrated " + llmEstimate + "m \u2192 " + calibrated + "m");
|
|
2063
2094
|
}
|
|
2064
2095
|
}
|
|
2065
2096
|
log("STEP_5c", "Minute calibration complete");
|
|
@@ -2084,7 +2115,8 @@ Return JSON:
|
|
|
2084
2115
|
return {
|
|
2085
2116
|
knowledgeGraph: verifiedGraph,
|
|
2086
2117
|
warnings,
|
|
2087
|
-
verificationReport: report
|
|
2118
|
+
verificationReport: report,
|
|
2119
|
+
provenance: client.callLog
|
|
2088
2120
|
};
|
|
2089
2121
|
}
|
|
2090
2122
|
|
|
@@ -2111,9 +2143,10 @@ async function generateHybridGraph(options) {
|
|
|
2111
2143
|
description: "",
|
|
2112
2144
|
total_project_minutes: 0,
|
|
2113
2145
|
total_concept_minutes: 0,
|
|
2114
|
-
|
|
2146
|
+
linked_prereq_minutes: 0
|
|
2115
2147
|
},
|
|
2116
|
-
warnings
|
|
2148
|
+
warnings,
|
|
2149
|
+
provenance: []
|
|
2117
2150
|
};
|
|
2118
2151
|
}
|
|
2119
2152
|
const client = createLlmClient(llmConfig);
|
|
@@ -2194,9 +2227,14 @@ async function generateHybridGraph(options) {
|
|
|
2194
2227
|
description: "Progressive completion path: " + phases.length + " phases, " + features.length + " features, " + concepts.length + " concepts",
|
|
2195
2228
|
total_project_minutes: totalProjectMinutes,
|
|
2196
2229
|
total_concept_minutes: totalConceptMinutes,
|
|
2197
|
-
|
|
2230
|
+
// HG-1 (2026-10-05): renamed from estimated_total_minutes. It counts only
|
|
2231
|
+
// concepts that HAVE a feature link (max per concept, not summed), so it
|
|
2232
|
+
// is NOT the course total — see HybridGraphSchema.linked_prereq_minutes.
|
|
2233
|
+
// The phase planner below commits to totalProjectMinutes + totalConceptMinutes.
|
|
2234
|
+
linked_prereq_minutes: totalProjectMinutes + totalPrereqMinutes
|
|
2198
2235
|
},
|
|
2199
|
-
warnings
|
|
2236
|
+
warnings,
|
|
2237
|
+
provenance: client.callLog
|
|
2200
2238
|
};
|
|
2201
2239
|
}
|
|
2202
2240
|
function auditPhasePlan(plan, features, concepts, links, warnings, log) {
|
|
@@ -2272,12 +2310,20 @@ function auditPhasePlan(plan, features, concepts, links, warnings, log) {
|
|
|
2272
2310
|
} else {
|
|
2273
2311
|
newIntroPhase.set(cid, order);
|
|
2274
2312
|
}
|
|
2313
|
+
introductions.push({ concept_id: cid, aspect, note: String(ri.note || "") });
|
|
2314
|
+
}
|
|
2315
|
+
for (const intro of introductions) {
|
|
2316
|
+
const cid = intro.concept_id;
|
|
2317
|
+
if (intro.aspect === "advanced") continue;
|
|
2318
|
+
const introOrder = newIntroPhase.get(cid);
|
|
2319
|
+
if (introOrder === void 0 || introOrder !== order) continue;
|
|
2275
2320
|
for (const pre of prereqOf.get(cid) || []) {
|
|
2276
|
-
|
|
2277
|
-
|
|
2321
|
+
const previewOrder = previewPhase.get(pre);
|
|
2322
|
+
const covered = newIntroPhase.has(pre) || previewOrder !== void 0 && previewOrder < order;
|
|
2323
|
+
if (!covered) {
|
|
2324
|
+
errors.push("Phase " + id + ": concept " + cid + " introduced before its prerequisite " + pre + " is introduced (or previewed in an earlier phase) - violates the prerequisite chain");
|
|
2278
2325
|
}
|
|
2279
2326
|
}
|
|
2280
|
-
introductions.push({ concept_id: cid, aspect, note: String(ri.note || "") });
|
|
2281
2327
|
}
|
|
2282
2328
|
phases.push({
|
|
2283
2329
|
id,
|
|
@@ -2315,6 +2361,9 @@ function auditPhasePlan(plan, features, concepts, links, warnings, log) {
|
|
|
2315
2361
|
if (anchor !== void 0 && anchor <= pOrder) {
|
|
2316
2362
|
errors.push("Concept " + cid + " previewed in phase " + pOrder + " but fully introduced in phase " + anchor + " - preview must precede the anchor introduction");
|
|
2317
2363
|
}
|
|
2364
|
+
if (anchor === void 0) {
|
|
2365
|
+
errors.push("Concept " + cid + " is previewed in phase " + pOrder + " but never fully introduced (aspect new) in any phase - a preview must have an anchor teach");
|
|
2366
|
+
}
|
|
2318
2367
|
}
|
|
2319
2368
|
return { phases, errors };
|
|
2320
2369
|
}
|
|
@@ -2371,6 +2420,6 @@ async function decomposePhases(args) {
|
|
|
2371
2420
|
return phases;
|
|
2372
2421
|
}
|
|
2373
2422
|
|
|
2374
|
-
export { CIO_CODE_RE, CONCEPT_CODE_RE, LEARNER_CLAUSE, SIO_CODE_RE, ULO_CODE_RE, assignConceptCodes, auditDepthLayers, auditPhasePlan, auditSyllabusCoverage, breakPrerequisiteCycles, cioActionSlug, cioCode, conceptCodeFromName, createLlmClient, detectPrerequisiteCycles, escalateAndMapConcepts, extractFeatureSteps, extractFeatureStepsBatched, extractProjectOverview, extractScaffold, generateHybridGraph, generateKnowledgeGraph, llmChatJson, sioCode, standardStatement, techTagFor, uloCode, verifyKnowledgeGraph, verifyProjectGraph };
|
|
2375
|
-
//# sourceMappingURL=chunk-
|
|
2376
|
-
//# sourceMappingURL=chunk-
|
|
2423
|
+
export { CIO_CODE_RE, CONCEPT_CODE_RE, LEARNER_CLAUSE, SIO_CODE_RE, ULO_CODE_RE, assignConceptCodes, auditDepthLayers, auditPhasePlan, auditSyllabusCoverage, breakPrerequisiteCycles, cioActionSlug, cioCode, conceptCodeFromName, createLlmClient, detectPrerequisiteCycles, escalateAndMapConcepts, extractFeatureSteps, extractFeatureStepsBatched, extractProjectOverview, extractScaffold, generateHybridGraph, generateKnowledgeGraph, llmChatJson, sioCode, standardStatement, summarizeProvenance, techTagFor, uloCode, verifyKnowledgeGraph, verifyProjectGraph };
|
|
2424
|
+
//# sourceMappingURL=chunk-2OGNQUXC.mjs.map
|
|
2425
|
+
//# sourceMappingURL=chunk-2OGNQUXC.mjs.map
|