@thanh01.pmt/curriculum-kit 1.0.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.
Files changed (68) hide show
  1. package/README.md +22 -0
  2. package/dist/ai/index.cjs +4980 -0
  3. package/dist/ai/index.cjs.map +1 -0
  4. package/dist/ai/index.d.cts +1287 -0
  5. package/dist/ai/index.d.ts +1287 -0
  6. package/dist/ai/index.mjs +4924 -0
  7. package/dist/ai/index.mjs.map +1 -0
  8. package/dist/index.cjs +15308 -0
  9. package/dist/index.cjs.map +1 -0
  10. package/dist/index.d.cts +747 -0
  11. package/dist/index.d.ts +747 -0
  12. package/dist/index.mjs +14965 -0
  13. package/dist/index.mjs.map +1 -0
  14. package/dist/media/index.cjs +1238 -0
  15. package/dist/media/index.cjs.map +1 -0
  16. package/dist/media/index.d.cts +254 -0
  17. package/dist/media/index.d.ts +254 -0
  18. package/dist/media/index.mjs +1221 -0
  19. package/dist/media/index.mjs.map +1 -0
  20. package/dist/milestoneBundleGenerator-CbXhs1CP.d.ts +62 -0
  21. package/dist/milestoneBundleGenerator-DrBtHzBO.d.cts +62 -0
  22. package/dist/pipeline/index.cjs +4894 -0
  23. package/dist/pipeline/index.cjs.map +1 -0
  24. package/dist/pipeline/index.d.cts +161 -0
  25. package/dist/pipeline/index.d.ts +161 -0
  26. package/dist/pipeline/index.mjs +4869 -0
  27. package/dist/pipeline/index.mjs.map +1 -0
  28. package/dist/provider-factory-DH3udYlN.d.cts +25 -0
  29. package/dist/provider-factory-DH3udYlN.d.ts +25 -0
  30. package/dist/publishers/index.cjs +575 -0
  31. package/dist/publishers/index.cjs.map +1 -0
  32. package/dist/publishers/index.d.cts +85 -0
  33. package/dist/publishers/index.d.ts +85 -0
  34. package/dist/publishers/index.mjs +561 -0
  35. package/dist/publishers/index.mjs.map +1 -0
  36. package/dist/schemas/index.cjs +1640 -0
  37. package/dist/schemas/index.cjs.map +1 -0
  38. package/dist/schemas/index.d.cts +14194 -0
  39. package/dist/schemas/index.d.ts +14194 -0
  40. package/dist/schemas/index.mjs +1517 -0
  41. package/dist/schemas/index.mjs.map +1 -0
  42. package/dist/standards/index.cjs +910 -0
  43. package/dist/standards/index.cjs.map +1 -0
  44. package/dist/standards/index.d.cts +130 -0
  45. package/dist/standards/index.d.ts +130 -0
  46. package/dist/standards/index.mjs +890 -0
  47. package/dist/standards/index.mjs.map +1 -0
  48. package/dist/standardsCoverageGate-BWAkXY76.d.cts +767 -0
  49. package/dist/standardsCoverageGate-BWAkXY76.d.ts +767 -0
  50. package/dist/storage/index.cjs +595 -0
  51. package/dist/storage/index.cjs.map +1 -0
  52. package/dist/storage/index.d.cts +77 -0
  53. package/dist/storage/index.d.ts +77 -0
  54. package/dist/storage/index.mjs +583 -0
  55. package/dist/storage/index.mjs.map +1 -0
  56. package/dist/streamRunner-C7j5LKon.d.cts +60 -0
  57. package/dist/streamRunner-C7j5LKon.d.ts +60 -0
  58. package/dist/supabasePublisher-C627qXFT.d.cts +63 -0
  59. package/dist/supabasePublisher-C627qXFT.d.ts +63 -0
  60. package/dist/types-BUJGYiep.d.cts +89 -0
  61. package/dist/types-BUJGYiep.d.ts +89 -0
  62. package/dist/workflow/index.cjs +5266 -0
  63. package/dist/workflow/index.cjs.map +1 -0
  64. package/dist/workflow/index.d.cts +295 -0
  65. package/dist/workflow/index.d.ts +295 -0
  66. package/dist/workflow/index.mjs +5216 -0
  67. package/dist/workflow/index.mjs.map +1 -0
  68. package/package.json +105 -0
@@ -0,0 +1,561 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { Octokit } from '@octokit/rest';
4
+ import { createClient } from '@supabase/supabase-js';
5
+
6
+ // src/publishers/localWorkspaceManager.ts
7
+ var LocalWorkspaceManager = class {
8
+ baseDir;
9
+ constructor(baseDir) {
10
+ this.baseDir = baseDir || path.resolve(process.cwd(), "output", "workspace-jobs");
11
+ }
12
+ getJobDirectory(jobId) {
13
+ return path.join(this.baseDir, jobId);
14
+ }
15
+ async initJobWorkspace(jobId, initialInfo) {
16
+ const jobDir = this.getJobDirectory(jobId);
17
+ await fs.mkdir(jobDir, { recursive: true });
18
+ const now = (/* @__PURE__ */ new Date()).toISOString();
19
+ const manifest = {
20
+ jobId,
21
+ courseTitle: initialInfo.courseTitle,
22
+ topic: initialInfo.topic,
23
+ language: initialInfo.language,
24
+ status: "IN_PROGRESS",
25
+ totalMilestones: initialInfo.totalMilestones,
26
+ completedMilestones: 0,
27
+ createdAt: now,
28
+ updatedAt: now,
29
+ artifacts: []
30
+ };
31
+ await this.saveManifest(jobId, manifest);
32
+ return jobDir;
33
+ }
34
+ async saveArtifact(jobId, milestoneSlug, filename, content, artifactType) {
35
+ const jobDir = this.getJobDirectory(jobId);
36
+ const targetDir = path.join(jobDir, milestoneSlug);
37
+ await fs.mkdir(targetDir, { recursive: true });
38
+ const filePath = path.join(targetDir, filename);
39
+ await fs.writeFile(filePath, content, "utf-8");
40
+ const stat = await fs.stat(filePath);
41
+ const manifest = await this.getManifest(jobId);
42
+ if (manifest) {
43
+ manifest.artifacts.push({
44
+ milestoneId: milestoneSlug,
45
+ artifactType,
46
+ filePath: path.relative(jobDir, filePath),
47
+ sizeBytes: stat.size,
48
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
49
+ });
50
+ manifest.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
51
+ await this.saveManifest(jobId, manifest);
52
+ }
53
+ return filePath;
54
+ }
55
+ async getManifest(jobId) {
56
+ const manifestPath = path.join(this.getJobDirectory(jobId), "manifest.json");
57
+ try {
58
+ const data = await fs.readFile(manifestPath, "utf-8");
59
+ return JSON.parse(data);
60
+ } catch {
61
+ return null;
62
+ }
63
+ }
64
+ async saveManifest(jobId, manifest) {
65
+ const manifestPath = path.join(this.getJobDirectory(jobId), "manifest.json");
66
+ manifest.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
67
+ await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
68
+ }
69
+ async markMilestoneCompleted(jobId) {
70
+ const manifest = await this.getManifest(jobId);
71
+ if (manifest) {
72
+ manifest.completedMilestones += 1;
73
+ if (manifest.completedMilestones >= manifest.totalMilestones) {
74
+ manifest.status = "COMPLETED";
75
+ }
76
+ await this.saveManifest(jobId, manifest);
77
+ }
78
+ }
79
+ async markJobFailed(jobId, errorMsg) {
80
+ const manifest = await this.getManifest(jobId);
81
+ if (manifest) {
82
+ manifest.status = "FAILED";
83
+ manifest.error = errorMsg;
84
+ await this.saveManifest(jobId, manifest);
85
+ }
86
+ }
87
+ };
88
+ async function publishToGitHub(options) {
89
+ const token = options.githubToken || process.env.GITHUB_TOKEN;
90
+ if (!token) {
91
+ return {
92
+ success: false,
93
+ branch: options.branch || "main",
94
+ publishedFilesCount: 0,
95
+ error: "Missing GITHUB_TOKEN in environment or options"
96
+ };
97
+ }
98
+ const octokit = new Octokit({ auth: token });
99
+ const targetBranch = options.branch || `generated/course-${options.jobId}`;
100
+ let publishedFilesCount = 0;
101
+ try {
102
+ const filesToUpload = [];
103
+ async function scanDir(currentDir) {
104
+ const entries = await fs.readdir(currentDir, { withFileTypes: true });
105
+ for (const entry of entries) {
106
+ const fullPath = path.join(currentDir, entry.name);
107
+ if (entry.isDirectory()) {
108
+ await scanDir(fullPath);
109
+ } else if (entry.isFile() && !entry.name.startsWith(".")) {
110
+ const content = await fs.readFile(fullPath, "utf-8");
111
+ const relPath = path.relative(options.localJobDir, fullPath);
112
+ filesToUpload.push({ path: relPath, content });
113
+ }
114
+ }
115
+ }
116
+ await scanDir(options.localJobDir);
117
+ if (filesToUpload.length === 0) {
118
+ return {
119
+ success: false,
120
+ branch: targetBranch,
121
+ publishedFilesCount: 0,
122
+ error: "No files found in workspace to upload"
123
+ };
124
+ }
125
+ const baseBranch = "main";
126
+ let baseTreeSha;
127
+ let baseCommitSha;
128
+ try {
129
+ const { data: refData } = await octokit.git.getRef({
130
+ owner: options.owner,
131
+ repo: options.repo,
132
+ ref: `heads/${baseBranch}`
133
+ });
134
+ baseCommitSha = refData.object.sha;
135
+ const { data: commitData } = await octokit.git.getCommit({
136
+ owner: options.owner,
137
+ repo: options.repo,
138
+ commit_sha: baseCommitSha
139
+ });
140
+ baseTreeSha = commitData.tree.sha;
141
+ } catch (e) {
142
+ return {
143
+ success: false,
144
+ branch: targetBranch,
145
+ publishedFilesCount: 0,
146
+ error: `Failed to fetch base branch ${baseBranch}: ${e.message}`
147
+ };
148
+ }
149
+ const treeItems = [];
150
+ for (const file of filesToUpload) {
151
+ const { data: blobData } = await octokit.git.createBlob({
152
+ owner: options.owner,
153
+ repo: options.repo,
154
+ content: Buffer.from(file.content).toString("base64"),
155
+ encoding: "base64"
156
+ });
157
+ treeItems.push({
158
+ path: file.path,
159
+ mode: "100644",
160
+ type: "blob",
161
+ sha: blobData.sha
162
+ });
163
+ publishedFilesCount++;
164
+ }
165
+ const { data: newTree } = await octokit.git.createTree({
166
+ owner: options.owner,
167
+ repo: options.repo,
168
+ base_tree: baseTreeSha,
169
+ tree: treeItems
170
+ });
171
+ const message = options.commitMessage || `feat(curriculum): publish generated course artifacts [job:${options.jobId}]`;
172
+ const { data: newCommit } = await octokit.git.createCommit({
173
+ owner: options.owner,
174
+ repo: options.repo,
175
+ message,
176
+ tree: newTree.sha,
177
+ parents: [baseCommitSha]
178
+ });
179
+ try {
180
+ await octokit.git.createRef({
181
+ owner: options.owner,
182
+ repo: options.repo,
183
+ ref: `refs/heads/${targetBranch}`,
184
+ sha: newCommit.sha
185
+ });
186
+ } catch {
187
+ await octokit.git.updateRef({
188
+ owner: options.owner,
189
+ repo: options.repo,
190
+ ref: `heads/${targetBranch}`,
191
+ sha: newCommit.sha,
192
+ force: true
193
+ });
194
+ }
195
+ let prUrl = void 0;
196
+ if (options.createPullRequest && targetBranch !== baseBranch) {
197
+ try {
198
+ const { data: prData } = await octokit.pulls.create({
199
+ owner: options.owner,
200
+ repo: options.repo,
201
+ title: `[Curriculum AI] Generated Curriculum (${options.jobId})`,
202
+ head: targetBranch,
203
+ base: baseBranch,
204
+ body: `Automated curriculum generation output from job \`${options.jobId}\`.
205
+ Contains ${publishedFilesCount} files.`
206
+ });
207
+ prUrl = prData.html_url;
208
+ } catch {
209
+ }
210
+ }
211
+ return {
212
+ success: true,
213
+ commitSha: newCommit.sha,
214
+ branch: targetBranch,
215
+ pullRequestUrl: prUrl,
216
+ publishedFilesCount
217
+ };
218
+ } catch (error) {
219
+ return {
220
+ success: false,
221
+ branch: targetBranch,
222
+ publishedFilesCount,
223
+ error: error.message || String(error)
224
+ };
225
+ }
226
+ }
227
+ async function publishToSupabase(options) {
228
+ const supabaseUrl = options.supabaseUrl || process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL;
229
+ const serviceKey = options.supabaseServiceKey || process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
230
+ if (!supabaseUrl || !serviceKey) {
231
+ return {
232
+ success: false,
233
+ syncedMilestonesCount: 0,
234
+ uploadedAssetsCount: 0,
235
+ error: "Missing SUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY"
236
+ };
237
+ }
238
+ const supabase = createClient(supabaseUrl, serviceKey);
239
+ options.bucketName || "learning_resources";
240
+ try {
241
+ const { data: lpData, error: lpError } = await supabase.from("learning_paths").upsert(
242
+ {
243
+ code: `LP-${options.jobId}`,
244
+ title: options.courseTitle,
245
+ description: `Auto-generated curriculum on ${options.topic}`,
246
+ language: options.language,
247
+ metadata: {
248
+ jobId: options.jobId,
249
+ topic: options.topic,
250
+ totalMilestones: options.milestones.length,
251
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
252
+ }
253
+ },
254
+ { onConflict: "code" }
255
+ ).select("id").single();
256
+ const learningPathId = lpData?.id;
257
+ let syncedMilestonesCount = 0;
258
+ for (const [index, m] of options.milestones.entries()) {
259
+ const { error: lessonError } = await supabase.from("lessons").upsert(
260
+ {
261
+ code: `L-${options.jobId}-${m.milestoneId}`,
262
+ title: m.milestoneName,
263
+ order_index: index + 1,
264
+ learning_path_id: learningPathId,
265
+ metadata: {
266
+ milestoneId: m.milestoneId,
267
+ lessonSlug: m.lessonSlug,
268
+ summary: m.summary
269
+ }
270
+ },
271
+ { onConflict: "code" }
272
+ );
273
+ if (!lessonError) {
274
+ syncedMilestonesCount++;
275
+ }
276
+ }
277
+ return {
278
+ success: true,
279
+ learningPathId,
280
+ syncedMilestonesCount,
281
+ uploadedAssetsCount: 0
282
+ };
283
+ } catch (error) {
284
+ return {
285
+ success: false,
286
+ syncedMilestonesCount: 0,
287
+ uploadedAssetsCount: 0,
288
+ error: error.message || String(error)
289
+ };
290
+ }
291
+ }
292
+ async function uploadAssetToBucket(filePath, storagePath, options) {
293
+ const supabaseUrl = options?.supabaseUrl || process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL;
294
+ const serviceKey = options?.supabaseServiceKey || process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
295
+ if (!supabaseUrl || !serviceKey) {
296
+ return { success: false, error: "Missing Supabase credentials" };
297
+ }
298
+ const supabase = createClient(supabaseUrl, serviceKey);
299
+ const bucketName = options?.bucketName || "course-materials";
300
+ try {
301
+ const fileBuffer = await fs.readFile(filePath);
302
+ const { error: uploadError } = await supabase.storage.from(bucketName).upload(storagePath, fileBuffer, { upsert: true });
303
+ if (uploadError) {
304
+ return { success: false, error: uploadError.message };
305
+ }
306
+ const { data } = supabase.storage.from(bucketName).getPublicUrl(storagePath);
307
+ return { success: true, publicUrl: data.publicUrl };
308
+ } catch (error) {
309
+ return { success: false, error: error.message || String(error) };
310
+ }
311
+ }
312
+
313
+ // src/publishers/packagingService.ts
314
+ function parseQuizMarkdown(content, lessonId) {
315
+ const questions = [];
316
+ const lines = content.split("\n");
317
+ let currentQuestion = null;
318
+ let qCounter = 1;
319
+ for (let i = 0; i < lines.length; i++) {
320
+ const line = lines[i].trim();
321
+ const qMatch = line.match(/^(?:###\s*)?(?:Câu|\*\*Câu|Question|\*\*Question|\d+\.)\s*(\d+)?[:\.]?\s*(.*)/i);
322
+ if (qMatch && (line.includes("?") || line.includes(":") || line.startsWith("###") || line.startsWith("**C\xE2u"))) {
323
+ if (currentQuestion && currentQuestion.questionText && currentQuestion.options) {
324
+ questions.push({
325
+ lessonId,
326
+ questionNumber: currentQuestion.questionNumber || qCounter++,
327
+ questionText: currentQuestion.questionText,
328
+ options: currentQuestion.options,
329
+ correctAnswer: currentQuestion.correctAnswer || "A",
330
+ explanation: currentQuestion.explanation || "",
331
+ timeLimitSeconds: 60
332
+ });
333
+ }
334
+ currentQuestion = {
335
+ lessonId,
336
+ questionNumber: qMatch[1] ? parseInt(qMatch[1]) : qCounter,
337
+ questionText: qMatch[2] ? qMatch[2].replace(/\*\*/g, "").trim() : line.replace(/^###|\*\*/g, "").trim(),
338
+ options: { A: "", B: "", C: "", D: "" },
339
+ correctAnswer: "A",
340
+ explanation: "",
341
+ timeLimitSeconds: 60
342
+ };
343
+ continue;
344
+ }
345
+ if (!currentQuestion) continue;
346
+ const optMatch = line.match(/^[-*]?\s*([A-D])[\.\)]\s*(.*)/i);
347
+ if (optMatch) {
348
+ const optKey = optMatch[1].toUpperCase();
349
+ let optText = optMatch[2].trim();
350
+ if (optText.includes("(\u0110\xE1p \xE1n \u0111\xFAng)") || optText.includes("\u2705") || optText.includes("(Correct)")) {
351
+ currentQuestion.correctAnswer = optKey;
352
+ optText = optText.replace(/\(Đáp án đúng\)|\(Correct\)|✅/g, "").trim();
353
+ }
354
+ if (currentQuestion.options) {
355
+ currentQuestion.options[optKey] = optText;
356
+ }
357
+ continue;
358
+ }
359
+ const ansMatch = line.match(/(?:Đáp án đúng|Đáp án|Correct Answer|Answer)[:\s\*]+([A-D])/i);
360
+ if (ansMatch) {
361
+ currentQuestion.correctAnswer = ansMatch[1].toUpperCase();
362
+ continue;
363
+ }
364
+ const expMatch = line.match(/(?:Giải thích|Explanation)[:\s\*]+(.*)/i);
365
+ if (expMatch) {
366
+ currentQuestion.explanation = expMatch[1].replace(/\*\*/g, "").trim();
367
+ continue;
368
+ }
369
+ }
370
+ if (currentQuestion && currentQuestion.questionText && currentQuestion.options) {
371
+ questions.push({
372
+ lessonId,
373
+ questionNumber: currentQuestion.questionNumber || qCounter,
374
+ questionText: currentQuestion.questionText,
375
+ options: currentQuestion.options,
376
+ correctAnswer: currentQuestion.correctAnswer || "A",
377
+ explanation: currentQuestion.explanation || "",
378
+ timeLimitSeconds: 60
379
+ });
380
+ }
381
+ return questions;
382
+ }
383
+ function formatQuizzesToCsv(questions, format = "kahoot") {
384
+ if (format === "kahoot") {
385
+ const header2 = "Question,Answer 1,Answer 2,Answer 3,Answer 4,Time limit (sec),Correct answer(s)\n";
386
+ const rows2 = questions.map((q) => {
387
+ const cleanQ = `"${q.questionText.replace(/"/g, '""')}"`;
388
+ const a1 = `"${(q.options.A || "").replace(/"/g, '""')}"`;
389
+ const a2 = `"${(q.options.B || "").replace(/"/g, '""')}"`;
390
+ const a3 = `"${(q.options.C || "").replace(/"/g, '""')}"`;
391
+ const a4 = `"${(q.options.D || "").replace(/"/g, '""')}"`;
392
+ const correctIndex = q.correctAnswer === "A" ? 1 : q.correctAnswer === "B" ? 2 : q.correctAnswer === "C" ? 3 : 4;
393
+ return `${cleanQ},${a1},${a2},${a3},${a4},${q.timeLimitSeconds || 60},${correctIndex}`;
394
+ }).join("\n");
395
+ return header2 + rows2;
396
+ }
397
+ if (format === "quizizz") {
398
+ const header2 = "Question Text,Question Type,Option 1,Option 2,Option 3,Option 4,Option 5,Correct Answer,Time in seconds\n";
399
+ const rows2 = questions.map((q) => {
400
+ const cleanQ = `"${q.questionText.replace(/"/g, '""')}"`;
401
+ const a1 = `"${(q.options.A || "").replace(/"/g, '""')}"`;
402
+ const a2 = `"${(q.options.B || "").replace(/"/g, '""')}"`;
403
+ const a3 = `"${(q.options.C || "").replace(/"/g, '""')}"`;
404
+ const a4 = `"${(q.options.D || "").replace(/"/g, '""')}"`;
405
+ const correctIndex = q.correctAnswer === "A" ? 1 : q.correctAnswer === "B" ? 2 : q.correctAnswer === "C" ? 3 : 4;
406
+ return `${cleanQ},Multiple Choice,${a1},${a2},${a3},${a4},,${correctIndex},${q.timeLimitSeconds || 60}`;
407
+ }).join("\n");
408
+ return header2 + rows2;
409
+ }
410
+ const header = "Lesson ID,Question Number,Question Text,Option A,Option B,Option C,Option D,Correct Answer,Explanation\n";
411
+ const rows = questions.map((q) => {
412
+ return `"${q.lessonId}",${q.questionNumber},"${q.questionText.replace(/"/g, '""')}","${(q.options.A || "").replace(/"/g, '""')}","${(q.options.B || "").replace(/"/g, '""')}","${(q.options.C || "").replace(/"/g, '""')}","${(q.options.D || "").replace(/"/g, '""')}","${q.correctAnswer}","${(q.explanation || "").replace(/"/g, '""')}"`;
413
+ }).join("\n");
414
+ return header + rows;
415
+ }
416
+ async function exportAllProjectQuizzes(storage, projectId, format = "kahoot") {
417
+ const lessons = await storage.listLessons(projectId);
418
+ const allQuestions = [];
419
+ for (const l of lessons) {
420
+ const unitCode = l.lessonId.split("_")[0] || "U01";
421
+ const possiblePaths = [
422
+ `_content/${unitCode}/QUIZ_${l.lessonId}.md`,
423
+ `lessons/${l.lessonId}/QUIZ_${l.lessonId}.md`,
424
+ `QUIZ_${l.lessonId}.md`
425
+ ];
426
+ for (const p of possiblePaths) {
427
+ const content = await storage.readArtifact(projectId, p);
428
+ if (content) {
429
+ const parsed = parseQuizMarkdown(content, l.lessonId);
430
+ allQuestions.push(...parsed);
431
+ break;
432
+ }
433
+ }
434
+ }
435
+ const csv = formatQuizzesToCsv(allQuestions, format);
436
+ await storage.saveArtifact(projectId, `_released/quizzes_${format}.csv`, csv);
437
+ await storage.saveArtifact(projectId, `_released/QUIZ_BANK.json`, JSON.stringify(allQuestions, null, 2));
438
+ return { csv, questions: allQuestions };
439
+ }
440
+ async function buildDeliveryPackages(storage, projectId, onProgress) {
441
+ onProgress?.("@packager", "\u{1F4E6} B\u1EAFt \u0111\u1EA7u quy tr\xECnh ki\u1EC3m tra ch\u1EA5t l\u01B0\u1EE3ng v\xE0 ph\xE2n ph\u1ED1i 4 G\xF3i B\xE0n Giao...");
442
+ const statusReport = await storage.getProjectStatus(projectId);
443
+ await storage.readSotDocument(projectId, "CURRICULUM_FRAMEWORK.md") || "";
444
+ const brief = await storage.readSotDocument(projectId, "PROJECT_BRIEF.md") || "";
445
+ const dateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
446
+ onProgress?.("@assessor", "\u{1F4CA} \u0110ang tr\xEDch xu\u1EA5t to\xE0n b\u1ED9 ng\xE2n h\xE0ng c\xE2u h\u1ECFi tr\u1EAFc nghi\u1EC7m sang chu\u1EA9n Kahoot & Canvas CSV...");
447
+ const { csv: quizzesCsv, questions: quizzesJson } = await exportAllProjectQuizzes(storage, projectId, "kahoot");
448
+ onProgress?.("@packager", "\u{1F468}\u200D\u{1F3EB} \u0110ang l\u1EAFp r\xE1p Teacher Pack (Gi\xE1o \xE1n, K\u1EBF ho\u1EA1ch b\xE0i d\u1EA1y, L\u1EDDi tho\u1EA1i)...");
449
+ const teacherHandbook = `---
450
+ id: "${projectId.toUpperCase()}-TEACHER-HANDBOOK"
451
+ title: "Teacher Handbook: ${projectId.toUpperCase()}"
452
+ type: "HANDBOOK"
453
+ phase: "P4"
454
+ date: "${dateStr}"
455
+ ---
456
+
457
+ # \u{1F468}\u200D\u{1F3EB} TEACHER HANDBOOK & SCRIPT GUIDE
458
+
459
+ ## 1. T\u1ED5ng Quan Kh\xF3a H\u1ECDc
460
+ ${brief.slice(0, 1e3)}
461
+
462
+ ## 2. Khung Ph\xE2n B\u1ED5 S\u01B0 Ph\u1EA1m 5E & Th\u1EDDi Gian Gi\u1EA3ng D\u1EA1y
463
+ - M\u1ED7i bu\u1ED5i h\u1ECDc chu\u1EA9n h\xF3a 90 ph\xFAt.
464
+ - Tu\xE2n th\u1EE7 nghi\xEAm ng\u1EB7t ti\u1EBFn tr\xECnh: Engage (10m) $\\rightarrow$ Explore (20m) $\\rightarrow$ Explain (25m) $\\rightarrow$ Elaborate (25m) $\\rightarrow$ Evaluate (10m).
465
+
466
+ ## 3. Danh M\u1EE5c B\xE0i Gi\u1EA3ng (Lesson Plans)
467
+ ${statusReport.lessons.map((l) => `- \`LESSON_${l.lessonId}.md\``).join("\n") || "- \u0110ang c\u1EADp nh\u1EADt"}
468
+ `;
469
+ await storage.saveArtifact(projectId, `_released/teacher_pack/TEACHER_HANDBOOK.md`, teacherHandbook);
470
+ onProgress?.("@packager", "\u{1F9D1}\u200D\u{1F393} \u0110ang \u0111\xF3ng g\xF3i Student Pack (Slide b\xE0i gi\u1EA3ng, Lab th\u1EF1c h\xE0nh, Phi\u1EBFu h\u1ECDc t\u1EADp)...");
471
+ const studentSyllabus = `---
472
+ id: "${projectId.toUpperCase()}-STUDENT-SYLLABUS"
473
+ title: "Student Syllabus: ${projectId.toUpperCase()}"
474
+ type: "SYLLABUS"
475
+ phase: "P4"
476
+ date: "${dateStr}"
477
+ ---
478
+
479
+ # \u{1F9D1}\u200D\u{1F393} H\u1ED2 S\u01A0 H\u1ECCC T\u1EACP & L\u1ED8 TR\xCCNH TH\u1EF0C H\xC0NH
480
+
481
+ ## 1. M\u1EE5c Ti\xEAu N\u0103ng L\u1EF1c \u0110\u1EA7u Ra
482
+ H\u1ECDc vi\xEAn sau khi ho\xE0n th\xE0nh kh\xF3a h\u1ECDc s\u1EBD l\xE0m ch\u1EE7 to\xE0n di\u1EC7n c\xE1c k\u1EF9 n\u0103ng v\xE0 ki\u1EBFn th\u1EE9c \u0111\xE3 \u0111\u1ECBnh ngh\u0129a trong \u0111\u1ED3 th\u1ECB tri th\u1EE9c.
483
+
484
+ ## 2. Danh M\u1EE5c Slide & Lab Th\u1EF1c H\xE0nh
485
+ ${statusReport.lessons.map((l) => `- **${l.lessonId}:** Slide \`SLIDE_${l.lessonId}.md\` + Lab \`ACT_${l.lessonId}.md\``).join("\n") || "- \u0110ang c\u1EADp nh\u1EADt"}
486
+ `;
487
+ await storage.saveArtifact(projectId, `_released/student_pack/STUDENT_SYLLABUS.md`, studentSyllabus);
488
+ onProgress?.("@packager", "\u{1F468}\u200D\u{1F469}\u200D\u{1F467} \u0110ang l\u1EADp B\xE1o c\xE1o B\xE0n giao Ph\u1EE5 huynh (Parent Pack)...");
489
+ const parentOverview = `---
490
+ id: "${projectId.toUpperCase()}-PARENT-OVERVIEW"
491
+ title: "Parent Overview: ${projectId.toUpperCase()}"
492
+ type: "REPORT"
493
+ phase: "P4"
494
+ date: "${dateStr}"
495
+ ---
496
+
497
+ # \u{1F468}\u200D\u{1F469}\u200D\u{1F467} B\xC1O C\xC1O TI\u1EBEN \u0110\u1ED8 & K\u1EBE HO\u1EA0CH H\u1ECCC T\u1EACP D\xC0NH CHO PH\u1EE4 HUYNH
498
+
499
+ - **T\xEAn ch\u01B0\u01A1ng tr\xECnh:** ${projectId.toUpperCase()}
500
+ - **Th\u1EDDi l\u01B0\u1EE3ng:** ${statusReport.totalLessons} b\xE0i h\u1ECDc (Chu\u1EA9n 90 ph\xFAt/bu\u1ED5i).
501
+ - **M\u1EE5c ti\xEAu ph\xE1t tri\u1EC3n:** Gi\xFAp h\u1ECDc sinh l\xE0m ch\u1EE7 t\u01B0 duy gi\u1EA3i quy\u1EBFt v\u1EA5n \u0111\u1EC1, t\u1EF1 tay ph\xE1t tri\u1EC3n c\xE1c \u0111\u1ED3 \xE1n c\xF4ng ngh\u1EC7 th\u1EF1c t\u1EBF v\xE0 t\u1EF1 tin tr\u01B0\u1EDBc c\xE1c k\u1EF3 thi \u0111\xE1nh gi\xE1 n\u0103ng l\u1EF1c.
502
+ `;
503
+ await storage.saveArtifact(projectId, `_released/parent_pack/COURSE_OVERVIEW.md`, parentOverview);
504
+ onProgress?.("@packager", "\u{1F3E2} \u0110ang ho\xE0n t\u1EA5t Client Pack & Bi\xEAn b\u1EA3n Nghi\u1EC7m thu B\xE0n giao (HANDOVER_DOCUMENT.md)...");
505
+ const handoverDoc = `---
506
+ id: "${projectId.toUpperCase()}-HANDOVER"
507
+ title: "Handover Document: ${projectId.toUpperCase()}"
508
+ type: "HANDOVER_DOCUMENT"
509
+ created_by: "Curriculum OS Packager"
510
+ phase: "P4"
511
+ deliverable: "P4-T1"
512
+ version: "v1.0"
513
+ date: "${dateStr}"
514
+ template_contract: "artifact-template-v1"
515
+ ---
516
+
517
+ # \u{1F3E2} BI\xCAN B\u1EA2N NGHI\u1EC6M THU & B\xC0N GIAO H\u1ECCC LI\u1EC6U (HANDOVER DOCUMENT)
518
+
519
+ ## 1. Th\xF4ng Tin Nghi\u1EC7m Thu D\u1EF1 \xC1n
520
+ - **M\xE3 d\u1EF1 \xE1n:** \`${projectId}\`
521
+ - **Ng\xE0y nghi\u1EC7m thu b\xE0n giao:** ${dateStr}
522
+ - **\u0110\u01A1n v\u1ECB ph\xE1t tri\u1EC3n:** Curriculum OS Automation Team
523
+ - **Ti\xEAu chu\u1EA9n ch\u1EA5t l\u01B0\u1EE3ng:** 100% Kh\u1EDBp m\u1EE5c ti\xEAu Bloom, Thang \u0111o 5E/EDP, Code s\u1EA1ch kh\xF4ng l\u1ED7i c\xFA ph\xE1p.
524
+
525
+ ## 2. Th\u1ED1ng K\xEA H\u1ECDc Li\u1EC7u B\xE0n Giao
526
+ - **T\xE0i li\u1EC7u N\u1EC1n t\u1EA3ng (SOT):** ${statusReport.sotReadiness.filter((s) => s.exists).length}/7 Files S\u1EB5n s\xE0ng (100%).
527
+ - **T\u1ED5ng s\u1ED1 b\xE0i h\u1ECDc ho\xE0n t\u1EA5t:** ${statusReport.completedCount}/${statusReport.totalLessons} b\xE0i h\u1ECDc.
528
+ - **T\u1ED5ng s\u1ED1 c\xE2u h\u1ECFi tr\u1EAFc nghi\u1EC7m tr\xEDch xu\u1EA5t:** ${quizzesJson.length} c\xE2u h\u1ECFi.
529
+ - **\u0110\u1ECBnh d\u1EA1ng xu\u1EA5t kh\u1EA9u:** Markdown chu\u1EA9n GitHub, Kahoot CSV, Canvas CSV, QUIZ_BANK JSON.
530
+
531
+ ## 3. C\u1EA5u Tr\xFAc 4 G\xF3i B\xE0n Giao \u0110\xE3 \u0110\xF3ng G\xF3i (\`_released/\`)
532
+ - \u{1F4C1} **\`_released/teacher_pack/\`**: \`TEACHER_HANDBOOK.md\` + To\xE0n b\u1ED9 Gi\xE1o \xE1n \`LESSON_*.md\`
533
+ - \u{1F4C1} **\`_released/student_pack/\`**: \`STUDENT_SYLLABUS.md\` + Slide \`SLIDE_*.md\` + Lab \`ACT_*.md\`
534
+ - \u{1F4C1} **\`_released/parent_pack/\`**: \`COURSE_OVERVIEW.md\`
535
+ - \u{1F4C1} **\`_released/client_pack/\`**: 7 Files SOT + \`ALIGNMENT_MATRIX.md\` + \`HANDOVER_DOCUMENT.md\`
536
+ - \u{1F4C1} **\`_released/quizzes_kahoot.csv\`**: Ng\xE2n h\xE0ng tr\u1EAFc nghi\u1EC7m chu\u1EA9n Kahoot.
537
+ - \u{1F4C1} **\`_released/QUIZ_BANK.json\`**: Ng\xE2n h\xE0ng JSON t\u01B0\u01A1ng th\xEDch Interactive Quiz Kit.
538
+
539
+ ---
540
+ - **\u0110\u1EA1i di\u1EC7n \u0110\u01A1n v\u1ECB Ph\xE1t tri\u1EC3n:** @packager (Curriculum OS)
541
+ - **Tr\u1EA1ng th\xE1i:** \u2705 **S\u1EB4N S\xC0NG TRI\u1EC2N KHAI TH\u01AF\u01A0NG M\u1EA0I (COMMERCIAL READY)**
542
+ `;
543
+ await storage.saveArtifact(projectId, `_released/client_pack/HANDOVER_DOCUMENT.md`, handoverDoc);
544
+ await storage.saveArtifact(projectId, `HANDOVER_DOCUMENT.md`, handoverDoc);
545
+ onProgress?.("@packager", `\u{1F389} B\xE0n giao th\xE0nh c\xF4ng to\xE0n b\u1ED9 4 g\xF3i h\u1ECDc li\u1EC7u v\xE0o th\u01B0 m\u1EE5c \`_released/\`!`);
546
+ return {
547
+ projectId,
548
+ handoverDoc,
549
+ teacherPackFiles: [`_released/teacher_pack/TEACHER_HANDBOOK.md`],
550
+ studentPackFiles: [`_released/student_pack/STUDENT_SYLLABUS.md`],
551
+ parentPackFiles: [`_released/parent_pack/COURSE_OVERVIEW.md`],
552
+ clientPackFiles: [`_released/client_pack/HANDOVER_DOCUMENT.md`, `HANDOVER_DOCUMENT.md`],
553
+ quizzesCsv,
554
+ quizzesJson,
555
+ timestamp: dateStr
556
+ };
557
+ }
558
+
559
+ export { LocalWorkspaceManager, buildDeliveryPackages, exportAllProjectQuizzes, formatQuizzesToCsv, parseQuizMarkdown, publishToGitHub, publishToSupabase, uploadAssetToBucket };
560
+ //# sourceMappingURL=index.mjs.map
561
+ //# sourceMappingURL=index.mjs.map