@thanh01.pmt/curriculum-kit 1.4.8 → 1.4.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +94 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -3
- package/dist/index.d.ts +6 -3
- package/dist/index.mjs +94 -29
- package/dist/index.mjs.map +1 -1
- package/dist/storage/index.cjs +37 -8
- package/dist/storage/index.cjs.map +1 -1
- package/dist/storage/index.mjs +37 -8
- package/dist/storage/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -365,6 +365,8 @@ interface LayerPrefillOptions {
|
|
|
365
365
|
timeoutMs?: number;
|
|
366
366
|
/** Idle (no-data) budget override for this prefill run (ms). */
|
|
367
367
|
idleTimeoutMs?: number;
|
|
368
|
+
/** Optional parent abort signal (e.g. from HTTP request) to stop immediately on client disconnect. */
|
|
369
|
+
signal?: AbortSignal;
|
|
368
370
|
}
|
|
369
371
|
interface PreliminaryResearchResult {
|
|
370
372
|
groundTruthSummary: string;
|
|
@@ -388,7 +390,7 @@ interface StreamBudget {
|
|
|
388
390
|
/** Abort when the whole stream exceeds this total wall time (ms). 0 disables. */
|
|
389
391
|
totalMs?: number;
|
|
390
392
|
}
|
|
391
|
-
declare const DEFAULT_STREAM_IDLE_MS =
|
|
393
|
+
declare const DEFAULT_STREAM_IDLE_MS = 180000;
|
|
392
394
|
declare const DEFAULT_STREAM_TOTAL_MS = 480000;
|
|
393
395
|
declare const LAYER_IDLE_BUDGET_MS: Record<number, number>;
|
|
394
396
|
/**
|
|
@@ -455,7 +457,7 @@ declare function createStreamChunkExtractor(): StreamChunkExtractor;
|
|
|
455
457
|
* createStreamChunkExtractor, whose latch is the authoritative dedupe.
|
|
456
458
|
*/
|
|
457
459
|
declare function extractStreamChunk(part: any): ExtractedStreamChunk;
|
|
458
|
-
declare function createStreamAbortSignal(budget: Required<StreamBudget
|
|
460
|
+
declare function createStreamAbortSignal(budget: Required<StreamBudget>, parentSignal?: AbortSignal): StreamAbortHandle;
|
|
459
461
|
/**
|
|
460
462
|
* Close an unterminated JSON object/array: append the delimiters that are
|
|
461
463
|
* still open, closing any open string first. Handles streams aborted
|
|
@@ -485,7 +487,7 @@ declare function safeParseJson<T = any>(rawText: string | null): T | null;
|
|
|
485
487
|
/**
|
|
486
488
|
* Multi-tiered resilient streaming inference helper using 100% Vercel AI SDK.
|
|
487
489
|
*/
|
|
488
|
-
declare function streamLLMWithFallback(systemPrompt: string, userPrompt: string, apiKeys?: Record<string, string>, onChunk?: LayerChunkCallback, preferredModel?: string, preferredProvider?: AIProviderName, budget?: StreamBudget): Promise<string | null>;
|
|
490
|
+
declare function streamLLMWithFallback(systemPrompt: string, userPrompt: string, apiKeys?: Record<string, string>, onChunk?: LayerChunkCallback, preferredModel?: string, preferredProvider?: AIProviderName, budget?: StreamBudget, signal?: AbortSignal): Promise<string | null>;
|
|
489
491
|
/**
|
|
490
492
|
* Conducts preliminary ecosystem research for a course project.
|
|
491
493
|
*/
|
|
@@ -540,6 +542,7 @@ declare function buildSessionSliceContext(plan: CurriculumPlan, lessonCode: stri
|
|
|
540
542
|
interface ProjectionMeta {
|
|
541
543
|
courseName: string;
|
|
542
544
|
shortDescription?: string;
|
|
545
|
+
targetLanguage?: string;
|
|
543
546
|
}
|
|
544
547
|
declare function renderFrameworkFromPlan(plan: CurriculumPlan, meta: ProjectionMeta): string;
|
|
545
548
|
/** Extract the glossary scope (terms per session) the EXPOSITION service consumes. */
|
package/dist/index.d.ts
CHANGED
|
@@ -365,6 +365,8 @@ interface LayerPrefillOptions {
|
|
|
365
365
|
timeoutMs?: number;
|
|
366
366
|
/** Idle (no-data) budget override for this prefill run (ms). */
|
|
367
367
|
idleTimeoutMs?: number;
|
|
368
|
+
/** Optional parent abort signal (e.g. from HTTP request) to stop immediately on client disconnect. */
|
|
369
|
+
signal?: AbortSignal;
|
|
368
370
|
}
|
|
369
371
|
interface PreliminaryResearchResult {
|
|
370
372
|
groundTruthSummary: string;
|
|
@@ -388,7 +390,7 @@ interface StreamBudget {
|
|
|
388
390
|
/** Abort when the whole stream exceeds this total wall time (ms). 0 disables. */
|
|
389
391
|
totalMs?: number;
|
|
390
392
|
}
|
|
391
|
-
declare const DEFAULT_STREAM_IDLE_MS =
|
|
393
|
+
declare const DEFAULT_STREAM_IDLE_MS = 180000;
|
|
392
394
|
declare const DEFAULT_STREAM_TOTAL_MS = 480000;
|
|
393
395
|
declare const LAYER_IDLE_BUDGET_MS: Record<number, number>;
|
|
394
396
|
/**
|
|
@@ -455,7 +457,7 @@ declare function createStreamChunkExtractor(): StreamChunkExtractor;
|
|
|
455
457
|
* createStreamChunkExtractor, whose latch is the authoritative dedupe.
|
|
456
458
|
*/
|
|
457
459
|
declare function extractStreamChunk(part: any): ExtractedStreamChunk;
|
|
458
|
-
declare function createStreamAbortSignal(budget: Required<StreamBudget
|
|
460
|
+
declare function createStreamAbortSignal(budget: Required<StreamBudget>, parentSignal?: AbortSignal): StreamAbortHandle;
|
|
459
461
|
/**
|
|
460
462
|
* Close an unterminated JSON object/array: append the delimiters that are
|
|
461
463
|
* still open, closing any open string first. Handles streams aborted
|
|
@@ -485,7 +487,7 @@ declare function safeParseJson<T = any>(rawText: string | null): T | null;
|
|
|
485
487
|
/**
|
|
486
488
|
* Multi-tiered resilient streaming inference helper using 100% Vercel AI SDK.
|
|
487
489
|
*/
|
|
488
|
-
declare function streamLLMWithFallback(systemPrompt: string, userPrompt: string, apiKeys?: Record<string, string>, onChunk?: LayerChunkCallback, preferredModel?: string, preferredProvider?: AIProviderName, budget?: StreamBudget): Promise<string | null>;
|
|
490
|
+
declare function streamLLMWithFallback(systemPrompt: string, userPrompt: string, apiKeys?: Record<string, string>, onChunk?: LayerChunkCallback, preferredModel?: string, preferredProvider?: AIProviderName, budget?: StreamBudget, signal?: AbortSignal): Promise<string | null>;
|
|
489
491
|
/**
|
|
490
492
|
* Conducts preliminary ecosystem research for a course project.
|
|
491
493
|
*/
|
|
@@ -540,6 +542,7 @@ declare function buildSessionSliceContext(plan: CurriculumPlan, lessonCode: stri
|
|
|
540
542
|
interface ProjectionMeta {
|
|
541
543
|
courseName: string;
|
|
542
544
|
shortDescription?: string;
|
|
545
|
+
targetLanguage?: string;
|
|
543
546
|
}
|
|
544
547
|
declare function renderFrameworkFromPlan(plan: CurriculumPlan, meta: ProjectionMeta): string;
|
|
545
548
|
/** Extract the glossary scope (terms per session) the EXPOSITION service consumes. */
|
package/dist/index.mjs
CHANGED
|
@@ -8406,13 +8406,16 @@ function computeContentHash(content) {
|
|
|
8406
8406
|
return "sha256:" + crypto.createHash("sha256").update(content.trim(), "utf-8").digest("hex");
|
|
8407
8407
|
}
|
|
8408
8408
|
var STANDARD_SOT_FILES = [
|
|
8409
|
-
"PROJECT_BRIEF.md",
|
|
8410
8409
|
"LEARNER_PROFILE.md",
|
|
8410
|
+
"PROJECT_BRIEF.md",
|
|
8411
|
+
"REFERENCE_PACK.md",
|
|
8412
|
+
"PROJECT_GRAPH.json",
|
|
8413
|
+
"HYBRID_GRAPH.json",
|
|
8414
|
+
"SECTION_LANGUAGE_CONTRACT.md",
|
|
8411
8415
|
"CURRICULUM_FRAMEWORK.md",
|
|
8412
|
-
"PROJECT_STATUS.md",
|
|
8413
8416
|
"CONTENT_STYLE_GUIDE.md",
|
|
8414
8417
|
"ART_DIRECTION.md",
|
|
8415
|
-
"
|
|
8418
|
+
"ALIGNMENT_MATRIX.md"
|
|
8416
8419
|
];
|
|
8417
8420
|
var FileSystemCurriculumAdapter = class {
|
|
8418
8421
|
baseDir;
|
|
@@ -8454,19 +8457,33 @@ var FileSystemCurriculumAdapter = class {
|
|
|
8454
8457
|
async listSotDocuments(projectId) {
|
|
8455
8458
|
const projectDir = this.getProjectDir(projectId);
|
|
8456
8459
|
const sotDir = path3.join(projectDir, "_sot");
|
|
8457
|
-
|
|
8460
|
+
const discoveredFiles = new Set(STANDARD_SOT_FILES);
|
|
8461
|
+
if (fs2.existsSync(sotDir)) {
|
|
8462
|
+
try {
|
|
8463
|
+
const diskFiles = fs2.readdirSync(sotDir);
|
|
8464
|
+
for (const f of diskFiles) {
|
|
8465
|
+
if (f.startsWith(".") || f.endsWith(".review.json")) continue;
|
|
8466
|
+
const fullPath = path3.join(sotDir, f);
|
|
8467
|
+
if (fs2.statSync(fullPath).isFile()) {
|
|
8468
|
+
discoveredFiles.add(f);
|
|
8469
|
+
}
|
|
8470
|
+
}
|
|
8471
|
+
} catch {
|
|
8472
|
+
}
|
|
8473
|
+
}
|
|
8474
|
+
return Array.from(discoveredFiles).map((filename) => {
|
|
8458
8475
|
const p = fs2.existsSync(path3.join(sotDir, filename)) ? path3.join(sotDir, filename) : fs2.existsSync(path3.join(projectDir, filename)) ? path3.join(projectDir, filename) : null;
|
|
8459
8476
|
if (p && fs2.existsSync(p)) {
|
|
8460
8477
|
const stats = fs2.statSync(p);
|
|
8461
8478
|
return {
|
|
8462
|
-
name: filename.replace(
|
|
8479
|
+
name: filename.replace(/\.(md|json)$/i, "").replace(/_/g, " "),
|
|
8463
8480
|
filename,
|
|
8464
8481
|
exists: true,
|
|
8465
8482
|
sizeBytes: stats.size
|
|
8466
8483
|
};
|
|
8467
8484
|
}
|
|
8468
8485
|
return {
|
|
8469
|
-
name: filename.replace(
|
|
8486
|
+
name: filename.replace(/\.(md|json)$/i, "").replace(/_/g, " "),
|
|
8470
8487
|
filename,
|
|
8471
8488
|
exists: false,
|
|
8472
8489
|
sizeBytes: 0
|
|
@@ -8712,11 +8729,23 @@ var SupabaseCurriculumAdapter = class {
|
|
|
8712
8729
|
return null;
|
|
8713
8730
|
}
|
|
8714
8731
|
async listSotDocuments(projectId) {
|
|
8732
|
+
const discoveredFiles = new Set(STANDARD_SOT_FILES);
|
|
8733
|
+
try {
|
|
8734
|
+
const { data, error } = await this.client.storage.from(this.bucketName).list(`${projectId}/_sot`);
|
|
8735
|
+
if (!error && data) {
|
|
8736
|
+
for (const item of data) {
|
|
8737
|
+
if (item.name && !item.name.startsWith(".") && !item.name.endsWith(".review.json")) {
|
|
8738
|
+
discoveredFiles.add(item.name);
|
|
8739
|
+
}
|
|
8740
|
+
}
|
|
8741
|
+
}
|
|
8742
|
+
} catch {
|
|
8743
|
+
}
|
|
8715
8744
|
const results = [];
|
|
8716
|
-
for (const filename of
|
|
8745
|
+
for (const filename of discoveredFiles) {
|
|
8717
8746
|
const content = await this.readSotDocument(projectId, filename);
|
|
8718
8747
|
results.push({
|
|
8719
|
-
name: filename.replace(
|
|
8748
|
+
name: filename.replace(/\.(md|json)$/i, "").replace(/_/g, " "),
|
|
8720
8749
|
filename,
|
|
8721
8750
|
exists: !!content,
|
|
8722
8751
|
content: content || void 0,
|
|
@@ -9093,10 +9122,12 @@ async function auditQualityReport(storage, projectId) {
|
|
|
9093
9122
|
computedScore += Math.round(bloomPassCount / total * 20);
|
|
9094
9123
|
computedScore += Math.round(Math.min(syntaxExecutableCount, total) / total * 20);
|
|
9095
9124
|
computedScore += Math.round(scopeCompletenessCount / total * 20);
|
|
9125
|
+
const sotCompletedCount = status.sotReadiness.filter((s) => s.exists).length;
|
|
9126
|
+
const sotTotalCount = status.sotReadiness.length;
|
|
9096
9127
|
if (sotReady) {
|
|
9097
|
-
details.push(
|
|
9128
|
+
details.push(`\u2705 N\u1EC1n t\u1EA3ng SOT (${sotCompletedCount}/${sotTotalCount} t\xE0i li\u1EC7u) s\u1EB5n s\xE0ng.`);
|
|
9098
9129
|
} else {
|
|
9099
|
-
details.push(
|
|
9130
|
+
details.push(`\u26A0\uFE0F C\u1EA7n ho\xE0n thi\u1EC7n \u0111\u1EA7y \u0111\u1EE7 t\xE0i li\u1EC7u SOT c\u01A1 s\u1EDF (${sotCompletedCount}/${sotTotalCount}).`);
|
|
9100
9131
|
}
|
|
9101
9132
|
details.push(`\u{1F3AF} Ph\u1EA1m vi h\u1ECDc li\u1EC7u theo SOT (Artifact Scope): [${artifactScope.join(", ")}].`);
|
|
9102
9133
|
if (pedagogy5EPass) {
|
|
@@ -9112,7 +9143,7 @@ async function auditQualityReport(storage, projectId) {
|
|
|
9112
9143
|
details.push(`\u2705 \u0110\u1ED9 ph\u1EE7 \u0111\u1EA7y \u0111\u1EE7 c\xE1c h\u1ECDc li\u1EC7u trong Artifact Scope: ${scopeCompletenessCount}/${total} b\xE0i h\u1ECDc.`);
|
|
9113
9144
|
const rawMarkdownReport = `## \u{1F6E1}\uFE0F B\xC1O C\xC1O KI\u1EC2M \u0110\u1ECANH CH\u1EA4T L\u01AF\u1EE2NG (QUALITY AUDIT): \`${projectId.toUpperCase()}\`
|
|
9114
9145
|
- **\u0110i\u1EC3m th\u1EA9m \u0111\u1ECBnh ch\u1EA5t l\u01B0\u1EE3ng:** **${computedScore}/100**
|
|
9115
|
-
- **Tr\u1EA1ng th\xE1i SOT:** ${sotReady ?
|
|
9146
|
+
- **Tr\u1EA1ng th\xE1i SOT:** ${sotReady ? `\u2705 ${sotCompletedCount}/${sotTotalCount} File SOT ho\xE0n t\u1EA5t` : `\u26A0\uFE0F C\u1EA7n b\u1ED5 sung t\xE0i li\u1EC7u SOT thi\u1EBFu (${sotCompletedCount}/${sotTotalCount})`}.
|
|
9116
9147
|
- **Ph\u1EA1m vi h\u1ECDc li\u1EC7u (SOT Artifact Scope):** \`${artifactScope.join(", ")}\`
|
|
9117
9148
|
- **C\u1EA5u tr\xFAc S\u01B0 ph\u1EA1m (5E / EDP):** ${pedagogy5ECount}/${total} b\xE0i h\u1ECDc \u0111\u1EA1t chu\u1EA9n ph\xE2n pha.
|
|
9118
9149
|
- **Thang \u0111o nh\u1EADn th\u1EE9c Bloom:** ${bloomPassCount}/${total} b\xE0i h\u1ECDc \u0111\u1EA1t ma tr\u1EADn m\u1EE5c ti\xEAu.
|
|
@@ -9214,7 +9245,9 @@ function extractScopeSequenceRows(framework) {
|
|
|
9214
9245
|
const trimmed = line.trim();
|
|
9215
9246
|
if (!trimmed.startsWith("|")) continue;
|
|
9216
9247
|
const lower = trimmed.toLowerCase();
|
|
9217
|
-
|
|
9248
|
+
const hasCodeCol = lower.includes("lesson code") || lower.includes("m\xE3 b\xE0i") || lower.includes("m\xE3 b\xE0i h\u1ECDc");
|
|
9249
|
+
const hasObjCol = lower.includes("learning objective") || lower.includes("m\u1EE5c ti\xEAu") || lower.includes("m\u1EE5c ti\xEAu h\u1ECDc t\u1EADp");
|
|
9250
|
+
if (hasCodeCol && hasObjCol) {
|
|
9218
9251
|
headerFound = true;
|
|
9219
9252
|
continue;
|
|
9220
9253
|
}
|
|
@@ -27258,13 +27291,13 @@ var DeterministicPipelineRunner = class {
|
|
|
27258
27291
|
|
|
27259
27292
|
// src/services/prefillService.ts
|
|
27260
27293
|
init_streamRunner();
|
|
27261
|
-
var DEFAULT_STREAM_IDLE_MS =
|
|
27294
|
+
var DEFAULT_STREAM_IDLE_MS = 18e4;
|
|
27262
27295
|
var DEFAULT_STREAM_TOTAL_MS = 48e4;
|
|
27263
27296
|
var LAYER_IDLE_BUDGET_MS = {
|
|
27264
|
-
1:
|
|
27265
|
-
2:
|
|
27297
|
+
1: 18e4,
|
|
27298
|
+
2: 18e4,
|
|
27266
27299
|
3: 18e4
|
|
27267
|
-
//
|
|
27300
|
+
// 3 minutes idle protection across all layers for free-tier reasoning models
|
|
27268
27301
|
};
|
|
27269
27302
|
var LAYER_TOTAL_BUDGET_MS = {
|
|
27270
27303
|
1: 48e4,
|
|
@@ -27325,10 +27358,20 @@ function extractStreamChunk(part) {
|
|
|
27325
27358
|
}
|
|
27326
27359
|
return {};
|
|
27327
27360
|
}
|
|
27328
|
-
function createStreamAbortSignal(budget) {
|
|
27361
|
+
function createStreamAbortSignal(budget, parentSignal) {
|
|
27329
27362
|
const controller = new AbortController();
|
|
27330
27363
|
let idleTimer = null;
|
|
27331
27364
|
let totalTimer = null;
|
|
27365
|
+
const onParentAbort = () => {
|
|
27366
|
+
controller.abort(parentSignal?.reason || new Error("Aborted by parent signal"));
|
|
27367
|
+
};
|
|
27368
|
+
if (parentSignal) {
|
|
27369
|
+
if (parentSignal.aborted) {
|
|
27370
|
+
controller.abort(parentSignal.reason || new Error("Parent signal already aborted"));
|
|
27371
|
+
} else {
|
|
27372
|
+
parentSignal.addEventListener("abort", onParentAbort, { once: true });
|
|
27373
|
+
}
|
|
27374
|
+
}
|
|
27332
27375
|
const armIdle = () => {
|
|
27333
27376
|
if (idleTimer) clearTimeout(idleTimer);
|
|
27334
27377
|
if (budget.idleMs > 0) {
|
|
@@ -27355,6 +27398,9 @@ function createStreamAbortSignal(budget) {
|
|
|
27355
27398
|
dispose: () => {
|
|
27356
27399
|
if (idleTimer) clearTimeout(idleTimer);
|
|
27357
27400
|
if (totalTimer) clearTimeout(totalTimer);
|
|
27401
|
+
if (parentSignal) {
|
|
27402
|
+
parentSignal.removeEventListener("abort", onParentAbort);
|
|
27403
|
+
}
|
|
27358
27404
|
idleTimer = null;
|
|
27359
27405
|
totalTimer = null;
|
|
27360
27406
|
}
|
|
@@ -27466,7 +27512,7 @@ function safeParseJson(rawText) {
|
|
|
27466
27512
|
}
|
|
27467
27513
|
return null;
|
|
27468
27514
|
}
|
|
27469
|
-
async function streamLLMWithFallback(systemPrompt, userPrompt, apiKeys = {}, onChunk, preferredModel, preferredProvider, budget) {
|
|
27515
|
+
async function streamLLMWithFallback(systemPrompt, userPrompt, apiKeys = {}, onChunk, preferredModel, preferredProvider, budget, signal) {
|
|
27470
27516
|
const alibabaKey = apiKeys.alibaba || apiKeys.dashscope || process.env.ALIBABA_API_KEY || process.env.DASHSCOPE_API_KEY;
|
|
27471
27517
|
const nvidiaKey = apiKeys.nvidia || process.env.NVIDIA_API_KEY;
|
|
27472
27518
|
const deepseekKey = apiKeys.deepseek || process.env.DEEPSEEK_API_KEY;
|
|
@@ -27498,7 +27544,6 @@ async function streamLLMWithFallback(systemPrompt, userPrompt, apiKeys = {}, onC
|
|
|
27498
27544
|
const nemotronModels = [
|
|
27499
27545
|
"nvidia/nemotron-3-ultra-550b-a55b",
|
|
27500
27546
|
"nvidia/nemotron-3.5-lightning-30b-a3b",
|
|
27501
|
-
"nvidia/nemotron-3-super-120b-a12b",
|
|
27502
27547
|
"nvidia/llama-3.1-nemotron-70b-instruct"
|
|
27503
27548
|
].filter((m) => isModelAllowed(m));
|
|
27504
27549
|
for (const m of nemotronModels) {
|
|
@@ -27549,8 +27594,12 @@ THINKING DIRECTIVE: Do ALL your reasoning internally BEFORE emitting output. Do
|
|
|
27549
27594
|
totalMs: budget?.totalMs ?? DEFAULT_STREAM_TOTAL_MS
|
|
27550
27595
|
};
|
|
27551
27596
|
for (const candidate of candidates) {
|
|
27597
|
+
if (signal?.aborted) {
|
|
27598
|
+
console.log(`[curriculum-kit:VercelAI] AbortSignal already aborted, cancelling candidate loop.`);
|
|
27599
|
+
break;
|
|
27600
|
+
}
|
|
27552
27601
|
const t0 = Date.now();
|
|
27553
|
-
const abort = createStreamAbortSignal(resolvedBudget);
|
|
27602
|
+
const abort = createStreamAbortSignal(resolvedBudget, signal);
|
|
27554
27603
|
try {
|
|
27555
27604
|
const modelInstance = getAIModel({
|
|
27556
27605
|
provider: candidate.provider,
|
|
@@ -27582,6 +27631,13 @@ THINKING DIRECTIVE: Do ALL your reasoning internally BEFORE emitting output. Do
|
|
|
27582
27631
|
}
|
|
27583
27632
|
} catch (e) {
|
|
27584
27633
|
console.warn(`[curriculum-kit:VercelAI] Candidate ${candidate.provider} (${candidate.model}) failed in ${Date.now() - t0}ms:`, e?.message || e);
|
|
27634
|
+
const isClientAborted = Boolean(
|
|
27635
|
+
signal?.aborted || e?.name === "AbortError" && signal?.aborted || e?.message && (e.message.includes("Controller is already closed") || e.message.includes("The operation was aborted") || e.message.includes("Aborted by parent signal"))
|
|
27636
|
+
);
|
|
27637
|
+
if (isClientAborted) {
|
|
27638
|
+
console.log(`[curriculum-kit:VercelAI] Client aborted or stream closed, halting candidate fallback loop.`);
|
|
27639
|
+
break;
|
|
27640
|
+
}
|
|
27585
27641
|
} finally {
|
|
27586
27642
|
abort.dispose();
|
|
27587
27643
|
}
|
|
@@ -27633,7 +27689,8 @@ Return concise JSON matching:
|
|
|
27633
27689
|
totalMs: options.timeoutMs,
|
|
27634
27690
|
model: options.model,
|
|
27635
27691
|
provider: options.provider
|
|
27636
|
-
})
|
|
27692
|
+
}),
|
|
27693
|
+
options.signal
|
|
27637
27694
|
);
|
|
27638
27695
|
let parsedResearch = {
|
|
27639
27696
|
groundTruthSummary: `Verified ecosystem standards for ${projectName}`,
|
|
@@ -27843,7 +27900,8 @@ REQUIREMENT: Synthesize ALL parameters above \u2014 every single line in FULL AC
|
|
|
27843
27900
|
totalMs: options.timeoutMs,
|
|
27844
27901
|
model: options.model,
|
|
27845
27902
|
provider: options.provider
|
|
27846
|
-
})
|
|
27903
|
+
}),
|
|
27904
|
+
options.signal
|
|
27847
27905
|
);
|
|
27848
27906
|
const parsed = safeParseJson(rawContent);
|
|
27849
27907
|
return {
|
|
@@ -27859,6 +27917,7 @@ var pad22 = (n) => String(n).padStart(2, "0");
|
|
|
27859
27917
|
var esc = (s) => s.replace(/\|/g, "/").replace(/\n/g, " ").trim();
|
|
27860
27918
|
var BLOOM_OF_DEPTH = { ulo: "Understand", cio: "Apply", sio: "Create" };
|
|
27861
27919
|
function renderFrameworkFromPlan(plan, meta) {
|
|
27920
|
+
const isVi = meta.targetLanguage === "vi";
|
|
27862
27921
|
const dateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
27863
27922
|
const totalSessions = plan.sessions.length;
|
|
27864
27923
|
const totalMinutes = plan.sessions.reduce(
|
|
@@ -27876,10 +27935,16 @@ function renderFrameworkFromPlan(plan, meta) {
|
|
|
27876
27935
|
const depthTops = [...new Set(s.depth_assignments.map((d) => d.depth))].map((d) => BLOOM_OF_DEPTH[d] ?? "Apply");
|
|
27877
27936
|
const bloom = depthTops[depthTops.length - 1] ?? "Apply";
|
|
27878
27937
|
const keyConcepts = s.new_keywords.length > 0 ? s.new_keywords.join(", ") : s.depth_assignments.map((d) => d.node_id).join(", ");
|
|
27879
|
-
const prereq = s.prerequisite_decisions.length > 0 ? s.prerequisite_decisions.filter((p) => p.decision === "taught_in_prior_lesson").length + " prior-taught, " + s.prerequisite_decisions.filter((p) => p.decision === "recap_in_lesson").length + " recap" : "None";
|
|
27938
|
+
const prereq = s.prerequisite_decisions.length > 0 ? s.prerequisite_decisions.filter((p) => p.decision === "taught_in_prior_lesson").length + " prior-taught, " + s.prerequisite_decisions.filter((p) => p.decision === "recap_in_lesson").length + " recap" : isVi ? "Kh\xF4ng" : "None";
|
|
27880
27939
|
const duration = s.knowledge_minutes + s.practice_minutes + s.overhead_minutes;
|
|
27881
27940
|
return "| " + pad22(i + 1) + " | " + s.id + " | " + esc(s.title) + " | " + s.unit_id + " | " + s.unit_id + "_M01 | " + esc(s.prose_objective) + " | " + esc(keyConcepts) + " | " + esc(s.exit_evidence[0] ?? "") + " | " + esc(prereq) + " | " + bloom + " | " + duration + " |";
|
|
27882
27941
|
}).join("\n");
|
|
27942
|
+
const courseOverviewHeader = isVi ? "## [REQUIRED] T\u1ED5ng quan Kh\xF3a h\u1ECDc" : "## [REQUIRED] Course Overview";
|
|
27943
|
+
const globalObjectivesHeader = isVi ? "## [REQUIRED] M\u1EE5c ti\xEAu H\u1ECDc t\u1EADp To\xE0n di\u1EC7n" : "## [REQUIRED] Global Learning Objectives";
|
|
27944
|
+
const structuralHierarchyHeader = isVi ? "## [REQUIRED] C\u1EA5u tr\xFAc Kh\xF3a h\u1ECDc (Unit & Module)" : "## [REQUIRED] Structural Hierarchy (Unit & Module)";
|
|
27945
|
+
const scopeSequenceHeader = isVi ? "## [REQUIRED] Ph\u1EA1m vi & Tr\xECnh t\u1EF1 Chi ti\u1EBFt (Scope & Sequence)" : "## [REQUIRED] Scope & Sequence (Detailed Roadmap)";
|
|
27946
|
+
const standardFormatHeader = isVi ? "## [REQUIRED] C\u1EA5u tr\xFAc B\xE0i h\u1ECDc Chu\u1EA9n" : "## [REQUIRED] Standard Lesson Format";
|
|
27947
|
+
const scopeTableHeader = isVi ? "| # | Lesson Code | Title | Unit | Module | Learning Objective (H\u1ECDc sinh l\xE0m \u0111\u01B0\u1EE3c g\xEC...) | Key Concepts (H\u1ECDc g\xEC) | Hands-on Deliverable (S\u1EA3n ph\u1EA9m bu\u1ED5i h\u1ECDc) | Prerequisites | Bloom | Duration (mins) |" : "| # | Lesson Code | Title | Unit | Module | Learning Objective (Students will be able to...) | Key Concepts (Hoc gi) | Hands-on Deliverable (Lam duoc gi) | Prerequisites | Bloom | Duration (mins) |";
|
|
27883
27948
|
return [
|
|
27884
27949
|
"---",
|
|
27885
27950
|
'id: "CURRICULUM-FRAMEWORK"',
|
|
@@ -27901,7 +27966,7 @@ function renderFrameworkFromPlan(plan, meta) {
|
|
|
27901
27966
|
"",
|
|
27902
27967
|
"_Projection of CURRICULUM_PLAN (hash " + plan.plan_hash + "). Approving this framework approves the per-session SCOPE recorded in the plan._",
|
|
27903
27968
|
"",
|
|
27904
|
-
|
|
27969
|
+
courseOverviewHeader,
|
|
27905
27970
|
"- **Official Course Name:** " + esc(meta.courseName),
|
|
27906
27971
|
meta.shortDescription ? "- **Short Description:** " + esc(meta.shortDescription) : "",
|
|
27907
27972
|
"- **Total Units:** " + plan.units.length,
|
|
@@ -27909,20 +27974,20 @@ function renderFrameworkFromPlan(plan, meta) {
|
|
|
27909
27974
|
"- **Total Duration:** ~" + totalHours + " hours (" + totalSessions + " sessions x " + plan.constraints.session_duration_minutes + " min).",
|
|
27910
27975
|
"- **Entry Level:** " + plan.constraints.entry_level + " (age band " + plan.constraints.age_band[0] + "-" + plan.constraints.age_band[1] + ").",
|
|
27911
27976
|
"",
|
|
27912
|
-
|
|
27977
|
+
globalObjectivesHeader,
|
|
27913
27978
|
...plan.course.objectives.map((o, i) => i + 1 + ". **[" + o.bloom + "]:** " + esc(o.statement)),
|
|
27914
27979
|
"",
|
|
27915
|
-
|
|
27980
|
+
structuralHierarchyHeader,
|
|
27916
27981
|
"| Unit | Module | Lessons | Module Objective | Lesson Codes |",
|
|
27917
27982
|
"|---|---|:---:|---|---|",
|
|
27918
27983
|
hierarchyRows || "| (none) | | | | |",
|
|
27919
27984
|
"",
|
|
27920
|
-
|
|
27921
|
-
|
|
27985
|
+
scopeSequenceHeader,
|
|
27986
|
+
scopeTableHeader,
|
|
27922
27987
|
"|:---:|:---:|---|---|---|---|---|---|---|:---:|:---:|",
|
|
27923
27988
|
scopeRows || "| 01 | U01_M01_L01 | (empty plan) | U01 | U01_M01 | - | - | - | None | Understand | " + plan.constraints.session_duration_minutes + " |",
|
|
27924
27989
|
"",
|
|
27925
|
-
|
|
27990
|
+
standardFormatHeader,
|
|
27926
27991
|
"- **Session duration:** " + plan.constraints.session_duration_minutes + " minutes (overhead " + String(plan.sessions[0]?.overhead_minutes ?? 0) + " min).",
|
|
27927
27992
|
"- **Knowledge/practice split per session:** see plan JSON (knowledge_minutes / practice_minutes).",
|
|
27928
27993
|
"- **Pedagogical flow:** 5E (Engage / Explore / Explain / Elaborate / Evaluate) unless policy overrides.",
|