@thanh01.pmt/curriculum-kit 1.4.47 → 1.4.48

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 CHANGED
@@ -20,3 +20,26 @@ A high-fidelity AI curriculum and pedagogical content generation engine powered
20
20
  ## 🌐 Language Policy
21
21
  - **100% English Canonical Prompts:** All system instructions and prompt builders are written strictly in English.
22
22
  - **Strict Target Output Language:** Every generation flow accepts `language` (`"vi"`, `"en"`, etc.), and the AI strictly outputs pedagogical explanations in the requested language while preserving international technical syntax and keywords.
23
+
24
+ ## 🧮 Planner Internals (P52, 2026-09-18)
25
+
26
+ `buildCurriculumPlan` turns a `CurriculumFeed` into a session plan in three ordered stages. All three are pure functions of the feed — no LLM calls.
27
+
28
+ ### 1. Depth reconcile — `reconcilePlanDepth` (services/depthReconciler.ts)
29
+
30
+ Runs **before** unit building/packing, only when `total_sessions` is fixed. When the bottom-up minute total exceeds the course budget (`total_sessions × session minutes × (1 − overhead_ratio)`), the planner previously had only one direction: stretch time (part-sessions). The reconciler adds the missing one — **lower knowledge depth along the Bruner spiral (SIO → CIO → ULO)** instead of cutting phases.
31
+
32
+ - **Downgrade priority (least pedagogical loss first):** (1) `advanced` revisit nodes (`__ADVxx` ids), (2) SIO→CIO on leaf concepts, (3) SIO→CIO on non-leaf non-core, (4) CIO→ULO on leaf, (5) CIO→ULO on non-leaf non-core; within a tier, largest `minutes_saved` first. Leaf-ness is computed at the **concept level** (`classifyDepthCandidates`: a concept is a leaf when no other concept lists it as a prerequisite; `__ADV`/`__PREV` ids map back to their base).
33
+ - **Absolute exclusions:** `is_core` concepts (Master-Tree core — escalate instead), non-concept nodes (product steps are build work, not knowledge depth), and nodes with **no declared `depth_hint`** (never guess).
34
+ - **Bound:** fixed-point loop capped at 5 rounds; mutates only `minutes`/`depth_hint` — no edges added/removed, no order changes, so the teaching-order invariant is preserved by construction.
35
+ - **Escalation:** when candidates are exhausted while still over budget, returns `escalate: true` + `deficitMinutes`; the planner surfaces a `budget_escalation: … human review required` warning — it **never silently cuts a phase**.
36
+
37
+ ### 2. Unit building + checkpoint-aware session cutting — `packUnitSessions`
38
+
39
+ Bins nodes into sessions at the content budget. When the next node would overflow, `cutBackToCheckpoint` searches for the **latest node whose cumulative minutes fall inside the window `[budget × (1 − SESSION_CUT_TOLERANCE), budget]`** (`SESSION_CUT_TOLERANCE = 0.15`, constants/pedagogy.ts) that carries a **`user_visible_deliverable`** (projected from the ProjectGraph step's `outcome.user_visible` by domain-kit). The cut lands on that checkpoint; displaced nodes are requeued in teaching order (nothing is dropped, wall-clock never exceeds the session length). Oversized nodes keep the existing multi-part split. **No checkpoint inside the window → raw minute cut + a `no-checkpoint-within-tolerance` warning** (the future hook for `scaffold_candidates`).
40
+
41
+ ### 3. ZPD audit
42
+
43
+ The single ZPD/overload audit pass runs **after** reconcile and packing, so it always sees the final node set — the reconciler cannot invalidate it by construction (it only shrinks minutes/depth, never adds new-concept events).
44
+
45
+ Planning nodes carry the reconcile inputs from the feed: `depth_variants` ({ulo, cio, sio} minute estimates), `depth_scaffold_candidates`, and `is_core`. See the domain-kit README (Schemas → `FeedNode`) for the feed-side contract.
package/dist/ai/index.cjs CHANGED
@@ -3727,7 +3727,25 @@ var PlanningNodeSchema = zod.z.object({
3727
3727
  file: zod.z.string(),
3728
3728
  evidence: zod.z.string().default("")
3729
3729
  })).default([]),
3730
- phase_id: zod.z.string().default("")
3730
+ phase_id: zod.z.string().default(""),
3731
+ // P52/T2.1: tangible user-visible outcome projected from the graph step
3732
+ // (checkpoint candidate for session cutting). Empty = not a checkpoint.
3733
+ user_visible_deliverable: zod.z.string().default(""),
3734
+ // P52/T3.1 — depth-reconcile inputs (mirrored from the feed; optional so
3735
+ // raw graphs without variants keep planning unchanged):
3736
+ // • depth_variants: minutes to teach this node at each depth level (ULO ≤
3737
+ // CIO ≤ SIO) — the reconciler's price list for downgrades.
3738
+ // • depth_scaffold_candidates: parallel to scaffold_candidates but acting
3739
+ // on DEPTH (SIO→CIO, CIO→ULO) instead of lesson time.
3740
+ // • is_core: Master-Tree core concept — never downgraded; escalate instead.
3741
+ depth_variants: zod.z.object({ ulo: zod.z.number().int().nonnegative(), cio: zod.z.number().int().nonnegative(), sio: zod.z.number().int().nonnegative() }).optional(),
3742
+ depth_scaffold_candidates: zod.z.array(zod.z.object({
3743
+ from_depth: DepthLevelSchema,
3744
+ to_depth: DepthLevelSchema,
3745
+ minutes_saved: zod.z.number().int().nonnegative(),
3746
+ reason: zod.z.string().default("")
3747
+ })).optional(),
3748
+ is_core: zod.z.boolean().default(false)
3731
3749
  });
3732
3750
  var DependencyEdgeSchema = zod.z.object({
3733
3751
  from: zod.z.string(),