@thanh01.pmt/domain-kit 0.2.0 → 0.3.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 (50) hide show
  1. package/dist/{chunk-RAR5MUUK.mjs → chunk-LKSVTRQE.mjs} +76 -6
  2. package/dist/chunk-LKSVTRQE.mjs.map +1 -0
  3. package/dist/{chunk-KPZKPNOJ.mjs → chunk-MCHHEBH3.mjs} +5 -5
  4. package/dist/{chunk-KPZKPNOJ.mjs.map → chunk-MCHHEBH3.mjs.map} +1 -1
  5. package/dist/{chunk-HH4UX65C.mjs → chunk-MSRKUZLY.mjs} +21 -4
  6. package/dist/chunk-MSRKUZLY.mjs.map +1 -0
  7. package/dist/{chunk-WHBAGJWN.mjs → chunk-VCPIWJ7R.mjs} +4 -4
  8. package/dist/chunk-VCPIWJ7R.mjs.map +1 -0
  9. package/dist/{chunk-JE57VFWX.mjs → chunk-Y7MRFFDJ.mjs} +33 -9
  10. package/dist/chunk-Y7MRFFDJ.mjs.map +1 -0
  11. package/dist/curriculumFeedEmitter-FYAWMPE6.mjs +4 -0
  12. package/dist/{curriculumFeedEmitter-UG664SWY.mjs.map → curriculumFeedEmitter-FYAWMPE6.mjs.map} +1 -1
  13. package/dist/{curriculumFeedSchema-TEeQg2bY.d.ts → curriculumFeedSchema-C8ZP3XMi.d.cts} +8 -8
  14. package/dist/{curriculumFeedSchema-TEeQg2bY.d.cts → curriculumFeedSchema-C8ZP3XMi.d.ts} +8 -8
  15. package/dist/feed/index.cjs +92 -5
  16. package/dist/feed/index.cjs.map +1 -1
  17. package/dist/feed/index.d.cts +1 -1
  18. package/dist/feed/index.d.ts +1 -1
  19. package/dist/feed/index.mjs +2 -2
  20. package/dist/graph/index.cjs +31 -7
  21. package/dist/graph/index.cjs.map +1 -1
  22. package/dist/graph/index.d.cts +2 -2
  23. package/dist/graph/index.d.ts +2 -2
  24. package/dist/graph/index.mjs +1 -1
  25. package/dist/{hybridGraphPipeline-DmWRKYXf.d.cts → hybridGraphPipeline-BhFyh3cm.d.cts} +1 -1
  26. package/dist/{hybridGraphPipeline-DVZYLCAQ.d.ts → hybridGraphPipeline-NZAWAPt_.d.ts} +1 -1
  27. package/dist/{hybridGraphSchema-BCgXicgA.d.ts → hybridGraphSchema-nrUeSwpU.d.cts} +15 -15
  28. package/dist/{hybridGraphSchema-BCgXicgA.d.cts → hybridGraphSchema-nrUeSwpU.d.ts} +15 -15
  29. package/dist/index.cjs +125 -14
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.cts +3 -3
  32. package/dist/index.d.ts +3 -3
  33. package/dist/index.mjs +5 -5
  34. package/dist/pipeline/index.cjs +123 -12
  35. package/dist/pipeline/index.cjs.map +1 -1
  36. package/dist/pipeline/index.d.cts +3 -3
  37. package/dist/pipeline/index.d.ts +3 -3
  38. package/dist/pipeline/index.mjs +4 -4
  39. package/dist/schemas/index.cjs +21 -4
  40. package/dist/schemas/index.cjs.map +1 -1
  41. package/dist/schemas/index.d.cts +2 -2
  42. package/dist/schemas/index.d.ts +2 -2
  43. package/dist/schemas/index.mjs +2 -2
  44. package/package.json +10 -9
  45. package/LICENSE +0 -21
  46. package/dist/chunk-HH4UX65C.mjs.map +0 -1
  47. package/dist/chunk-JE57VFWX.mjs.map +0 -1
  48. package/dist/chunk-RAR5MUUK.mjs.map +0 -1
  49. package/dist/chunk-WHBAGJWN.mjs.map +0 -1
  50. package/dist/curriculumFeedEmitter-UG664SWY.mjs +0 -4
@@ -1,11 +1,11 @@
1
1
  import { L as LlmClientConfig } from '../llmClient-ysPhLjcH.cjs';
2
2
  import { d as Feature, c as Step } from '../projectGraphSchema-DnD7orZV.cjs';
3
3
  import { K as Keyword } from '../keywordExtractor-BxlGRMHC.cjs';
4
- export { H as Hallucination, i as HybridGraphPipelineOptions, j as HybridGraphPipelineResult, K as KnowledgeGraphPipelineOptions, a as KnowledgeGraphPipelineResult, f as KnowledgeGraphVerificationReport, V as VerifyOptions, e as auditSyllabusCoverage, c as breakPrerequisiteCycles, d as detectPrerequisiteCycles, h as generateHybridGraph, g as generateKnowledgeGraph, b as verifyKnowledgeGraph, v as verifyProjectGraph } from '../hybridGraphPipeline-DmWRKYXf.cjs';
4
+ export { H as Hallucination, i as HybridGraphPipelineOptions, j as HybridGraphPipelineResult, K as KnowledgeGraphPipelineOptions, a as KnowledgeGraphPipelineResult, f as KnowledgeGraphVerificationReport, V as VerifyOptions, e as auditSyllabusCoverage, c as breakPrerequisiteCycles, d as detectPrerequisiteCycles, h as generateHybridGraph, g as generateKnowledgeGraph, b as verifyKnowledgeGraph, v as verifyProjectGraph } from '../hybridGraphPipeline-BhFyh3cm.cjs';
5
5
  export { C as ConceptMapping, E as EscalateOptions, e as escalateAndMapConcepts } from '../conceptEscalator-ltRLtPhf.cjs';
6
6
  import 'zod';
7
7
  import '../cpp-CZUPqJt9.cjs';
8
- import '../hybridGraphSchema-BCgXicgA.cjs';
8
+ import '../hybridGraphSchema-nrUeSwpU.cjs';
9
9
  import '../syllabusParser-MSX1fEzW.cjs';
10
10
 
11
11
  /**
@@ -1,11 +1,11 @@
1
1
  import { L as LlmClientConfig } from '../llmClient-ysPhLjcH.js';
2
2
  import { d as Feature, c as Step } from '../projectGraphSchema-DnD7orZV.js';
3
3
  import { K as Keyword } from '../keywordExtractor-DU5XRN8-.js';
4
- export { H as Hallucination, i as HybridGraphPipelineOptions, j as HybridGraphPipelineResult, K as KnowledgeGraphPipelineOptions, a as KnowledgeGraphPipelineResult, f as KnowledgeGraphVerificationReport, V as VerifyOptions, e as auditSyllabusCoverage, c as breakPrerequisiteCycles, d as detectPrerequisiteCycles, h as generateHybridGraph, g as generateKnowledgeGraph, b as verifyKnowledgeGraph, v as verifyProjectGraph } from '../hybridGraphPipeline-DVZYLCAQ.js';
4
+ export { H as Hallucination, i as HybridGraphPipelineOptions, j as HybridGraphPipelineResult, K as KnowledgeGraphPipelineOptions, a as KnowledgeGraphPipelineResult, f as KnowledgeGraphVerificationReport, V as VerifyOptions, e as auditSyllabusCoverage, c as breakPrerequisiteCycles, d as detectPrerequisiteCycles, h as generateHybridGraph, g as generateKnowledgeGraph, b as verifyKnowledgeGraph, v as verifyProjectGraph } from '../hybridGraphPipeline-NZAWAPt_.js';
5
5
  export { C as ConceptMapping, E as EscalateOptions, e as escalateAndMapConcepts } from '../conceptEscalator-YfMKIKCZ.js';
6
6
  import 'zod';
7
7
  import '../cpp-CZUPqJt9.js';
8
- import '../hybridGraphSchema-BCgXicgA.js';
8
+ import '../hybridGraphSchema-nrUeSwpU.js';
9
9
  import '../syllabusParser-MSX1fEzW.js';
10
10
 
11
11
  /**
@@ -1,5 +1,5 @@
1
1
  import '../chunk-ZKLT27T4.mjs';
2
- export { CIO_CODE_RE, CONCEPT_CODE_RE, LEARNER_CLAUSE, SIO_CODE_RE, ULO_CODE_RE, assignConceptCodes, auditDepthLayers, auditSyllabusCoverage, breakPrerequisiteCycles, cioActionSlug, cioCode, conceptCodeFromName, detectPrerequisiteCycles, escalateAndMapConcepts, extractFeatureSteps, extractFeatureStepsBatched, extractProjectOverview, extractScaffold, generateHybridGraph, generateKnowledgeGraph, sioCode, standardStatement, techTagFor, uloCode, verifyKnowledgeGraph, verifyProjectGraph } from '../chunk-JE57VFWX.mjs';
2
+ export { CIO_CODE_RE, CONCEPT_CODE_RE, LEARNER_CLAUSE, SIO_CODE_RE, ULO_CODE_RE, assignConceptCodes, auditDepthLayers, auditSyllabusCoverage, breakPrerequisiteCycles, cioActionSlug, cioCode, conceptCodeFromName, detectPrerequisiteCycles, escalateAndMapConcepts, extractFeatureSteps, extractFeatureStepsBatched, extractProjectOverview, extractScaffold, generateHybridGraph, generateKnowledgeGraph, sioCode, standardStatement, techTagFor, uloCode, verifyKnowledgeGraph, verifyProjectGraph } from '../chunk-Y7MRFFDJ.mjs';
3
3
  import '../chunk-T4Y2DE2W.mjs';
4
4
  //# sourceMappingURL=index.mjs.map
5
5
  //# sourceMappingURL=index.mjs.map
@@ -1,6 +1,6 @@
1
1
  import { u as ProjectGraph } from './projectGraphSchema-DnD7orZV.cjs';
2
2
  import { L as LlmClientConfig } from './llmClient-ysPhLjcH.cjs';
3
- import { g as KnowledgeGraph, b as Concept, j as HybridGraph } from './hybridGraphSchema-BCgXicgA.cjs';
3
+ import { g as KnowledgeGraph, b as Concept, j as HybridGraph } from './hybridGraphSchema-nrUeSwpU.cjs';
4
4
  import { P as ParsedSyllabus } from './syllabusParser-MSX1fEzW.cjs';
5
5
 
6
6
  /**
@@ -1,6 +1,6 @@
1
1
  import { u as ProjectGraph } from './projectGraphSchema-DnD7orZV.js';
2
2
  import { L as LlmClientConfig } from './llmClient-ysPhLjcH.js';
3
- import { g as KnowledgeGraph, b as Concept, j as HybridGraph } from './hybridGraphSchema-BCgXicgA.js';
3
+ import { g as KnowledgeGraph, b as Concept, j as HybridGraph } from './hybridGraphSchema-nrUeSwpU.js';
4
4
  import { P as ParsedSyllabus } from './syllabusParser-MSX1fEzW.js';
5
5
 
6
6
  /**
@@ -563,15 +563,15 @@ declare const ProjectConceptLinkSchema: z.ZodObject<{
563
563
  }>;
564
564
  declare const ConceptIntroductionSchema: z.ZodObject<{
565
565
  concept_id: z.ZodString;
566
- aspect: z.ZodDefault<z.ZodEnum<["new", "advanced"]>>;
566
+ aspect: z.ZodDefault<z.ZodEnum<["new", "advanced", "preview"]>>;
567
567
  note: z.ZodDefault<z.ZodOptional<z.ZodString>>;
568
568
  }, "strip", z.ZodTypeAny, {
569
569
  concept_id: string;
570
- aspect: "new" | "advanced";
570
+ aspect: "new" | "advanced" | "preview";
571
571
  note: string;
572
572
  }, {
573
573
  concept_id: string;
574
- aspect?: "new" | "advanced" | undefined;
574
+ aspect?: "new" | "advanced" | "preview" | undefined;
575
575
  note?: string | undefined;
576
576
  }>;
577
577
  declare const DevelopmentPhaseSchema: z.ZodObject<{
@@ -582,15 +582,15 @@ declare const DevelopmentPhaseSchema: z.ZodObject<{
582
582
  feature_ids: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
583
583
  introduces: z.ZodDefault<z.ZodArray<z.ZodObject<{
584
584
  concept_id: z.ZodString;
585
- aspect: z.ZodDefault<z.ZodEnum<["new", "advanced"]>>;
585
+ aspect: z.ZodDefault<z.ZodEnum<["new", "advanced", "preview"]>>;
586
586
  note: z.ZodDefault<z.ZodOptional<z.ZodString>>;
587
587
  }, "strip", z.ZodTypeAny, {
588
588
  concept_id: string;
589
- aspect: "new" | "advanced";
589
+ aspect: "new" | "advanced" | "preview";
590
590
  note: string;
591
591
  }, {
592
592
  concept_id: string;
593
- aspect?: "new" | "advanced" | undefined;
593
+ aspect?: "new" | "advanced" | "preview" | undefined;
594
594
  note?: string | undefined;
595
595
  }>, "many">>;
596
596
  }, "strip", z.ZodTypeAny, {
@@ -601,7 +601,7 @@ declare const DevelopmentPhaseSchema: z.ZodObject<{
601
601
  product_completion: string;
602
602
  introduces: {
603
603
  concept_id: string;
604
- aspect: "new" | "advanced";
604
+ aspect: "new" | "advanced" | "preview";
605
605
  note: string;
606
606
  }[];
607
607
  }, {
@@ -612,7 +612,7 @@ declare const DevelopmentPhaseSchema: z.ZodObject<{
612
612
  feature_ids?: string[] | undefined;
613
613
  introduces?: {
614
614
  concept_id: string;
615
- aspect?: "new" | "advanced" | undefined;
615
+ aspect?: "new" | "advanced" | "preview" | undefined;
616
616
  note?: string | undefined;
617
617
  }[] | undefined;
618
618
  }>;
@@ -2211,15 +2211,15 @@ declare const HybridGraphSchema: z.ZodObject<{
2211
2211
  feature_ids: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
2212
2212
  introduces: z.ZodDefault<z.ZodArray<z.ZodObject<{
2213
2213
  concept_id: z.ZodString;
2214
- aspect: z.ZodDefault<z.ZodEnum<["new", "advanced"]>>;
2214
+ aspect: z.ZodDefault<z.ZodEnum<["new", "advanced", "preview"]>>;
2215
2215
  note: z.ZodDefault<z.ZodOptional<z.ZodString>>;
2216
2216
  }, "strip", z.ZodTypeAny, {
2217
2217
  concept_id: string;
2218
- aspect: "new" | "advanced";
2218
+ aspect: "new" | "advanced" | "preview";
2219
2219
  note: string;
2220
2220
  }, {
2221
2221
  concept_id: string;
2222
- aspect?: "new" | "advanced" | undefined;
2222
+ aspect?: "new" | "advanced" | "preview" | undefined;
2223
2223
  note?: string | undefined;
2224
2224
  }>, "many">>;
2225
2225
  }, "strip", z.ZodTypeAny, {
@@ -2230,7 +2230,7 @@ declare const HybridGraphSchema: z.ZodObject<{
2230
2230
  product_completion: string;
2231
2231
  introduces: {
2232
2232
  concept_id: string;
2233
- aspect: "new" | "advanced";
2233
+ aspect: "new" | "advanced" | "preview";
2234
2234
  note: string;
2235
2235
  }[];
2236
2236
  }, {
@@ -2241,7 +2241,7 @@ declare const HybridGraphSchema: z.ZodObject<{
2241
2241
  feature_ids?: string[] | undefined;
2242
2242
  introduces?: {
2243
2243
  concept_id: string;
2244
- aspect?: "new" | "advanced" | undefined;
2244
+ aspect?: "new" | "advanced" | "preview" | undefined;
2245
2245
  note?: string | undefined;
2246
2246
  }[] | undefined;
2247
2247
  }>, "many">>>;
@@ -2533,7 +2533,7 @@ declare const HybridGraphSchema: z.ZodObject<{
2533
2533
  product_completion: string;
2534
2534
  introduces: {
2535
2535
  concept_id: string;
2536
- aspect: "new" | "advanced";
2536
+ aspect: "new" | "advanced" | "preview";
2537
2537
  note: string;
2538
2538
  }[];
2539
2539
  }[];
@@ -2824,7 +2824,7 @@ declare const HybridGraphSchema: z.ZodObject<{
2824
2824
  feature_ids?: string[] | undefined;
2825
2825
  introduces?: {
2826
2826
  concept_id: string;
2827
- aspect?: "new" | "advanced" | undefined;
2827
+ aspect?: "new" | "advanced" | "preview" | undefined;
2828
2828
  note?: string | undefined;
2829
2829
  }[] | undefined;
2830
2830
  }[] | undefined;
@@ -563,15 +563,15 @@ declare const ProjectConceptLinkSchema: z.ZodObject<{
563
563
  }>;
564
564
  declare const ConceptIntroductionSchema: z.ZodObject<{
565
565
  concept_id: z.ZodString;
566
- aspect: z.ZodDefault<z.ZodEnum<["new", "advanced"]>>;
566
+ aspect: z.ZodDefault<z.ZodEnum<["new", "advanced", "preview"]>>;
567
567
  note: z.ZodDefault<z.ZodOptional<z.ZodString>>;
568
568
  }, "strip", z.ZodTypeAny, {
569
569
  concept_id: string;
570
- aspect: "new" | "advanced";
570
+ aspect: "new" | "advanced" | "preview";
571
571
  note: string;
572
572
  }, {
573
573
  concept_id: string;
574
- aspect?: "new" | "advanced" | undefined;
574
+ aspect?: "new" | "advanced" | "preview" | undefined;
575
575
  note?: string | undefined;
576
576
  }>;
577
577
  declare const DevelopmentPhaseSchema: z.ZodObject<{
@@ -582,15 +582,15 @@ declare const DevelopmentPhaseSchema: z.ZodObject<{
582
582
  feature_ids: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
583
583
  introduces: z.ZodDefault<z.ZodArray<z.ZodObject<{
584
584
  concept_id: z.ZodString;
585
- aspect: z.ZodDefault<z.ZodEnum<["new", "advanced"]>>;
585
+ aspect: z.ZodDefault<z.ZodEnum<["new", "advanced", "preview"]>>;
586
586
  note: z.ZodDefault<z.ZodOptional<z.ZodString>>;
587
587
  }, "strip", z.ZodTypeAny, {
588
588
  concept_id: string;
589
- aspect: "new" | "advanced";
589
+ aspect: "new" | "advanced" | "preview";
590
590
  note: string;
591
591
  }, {
592
592
  concept_id: string;
593
- aspect?: "new" | "advanced" | undefined;
593
+ aspect?: "new" | "advanced" | "preview" | undefined;
594
594
  note?: string | undefined;
595
595
  }>, "many">>;
596
596
  }, "strip", z.ZodTypeAny, {
@@ -601,7 +601,7 @@ declare const DevelopmentPhaseSchema: z.ZodObject<{
601
601
  product_completion: string;
602
602
  introduces: {
603
603
  concept_id: string;
604
- aspect: "new" | "advanced";
604
+ aspect: "new" | "advanced" | "preview";
605
605
  note: string;
606
606
  }[];
607
607
  }, {
@@ -612,7 +612,7 @@ declare const DevelopmentPhaseSchema: z.ZodObject<{
612
612
  feature_ids?: string[] | undefined;
613
613
  introduces?: {
614
614
  concept_id: string;
615
- aspect?: "new" | "advanced" | undefined;
615
+ aspect?: "new" | "advanced" | "preview" | undefined;
616
616
  note?: string | undefined;
617
617
  }[] | undefined;
618
618
  }>;
@@ -2211,15 +2211,15 @@ declare const HybridGraphSchema: z.ZodObject<{
2211
2211
  feature_ids: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
2212
2212
  introduces: z.ZodDefault<z.ZodArray<z.ZodObject<{
2213
2213
  concept_id: z.ZodString;
2214
- aspect: z.ZodDefault<z.ZodEnum<["new", "advanced"]>>;
2214
+ aspect: z.ZodDefault<z.ZodEnum<["new", "advanced", "preview"]>>;
2215
2215
  note: z.ZodDefault<z.ZodOptional<z.ZodString>>;
2216
2216
  }, "strip", z.ZodTypeAny, {
2217
2217
  concept_id: string;
2218
- aspect: "new" | "advanced";
2218
+ aspect: "new" | "advanced" | "preview";
2219
2219
  note: string;
2220
2220
  }, {
2221
2221
  concept_id: string;
2222
- aspect?: "new" | "advanced" | undefined;
2222
+ aspect?: "new" | "advanced" | "preview" | undefined;
2223
2223
  note?: string | undefined;
2224
2224
  }>, "many">>;
2225
2225
  }, "strip", z.ZodTypeAny, {
@@ -2230,7 +2230,7 @@ declare const HybridGraphSchema: z.ZodObject<{
2230
2230
  product_completion: string;
2231
2231
  introduces: {
2232
2232
  concept_id: string;
2233
- aspect: "new" | "advanced";
2233
+ aspect: "new" | "advanced" | "preview";
2234
2234
  note: string;
2235
2235
  }[];
2236
2236
  }, {
@@ -2241,7 +2241,7 @@ declare const HybridGraphSchema: z.ZodObject<{
2241
2241
  feature_ids?: string[] | undefined;
2242
2242
  introduces?: {
2243
2243
  concept_id: string;
2244
- aspect?: "new" | "advanced" | undefined;
2244
+ aspect?: "new" | "advanced" | "preview" | undefined;
2245
2245
  note?: string | undefined;
2246
2246
  }[] | undefined;
2247
2247
  }>, "many">>>;
@@ -2533,7 +2533,7 @@ declare const HybridGraphSchema: z.ZodObject<{
2533
2533
  product_completion: string;
2534
2534
  introduces: {
2535
2535
  concept_id: string;
2536
- aspect: "new" | "advanced";
2536
+ aspect: "new" | "advanced" | "preview";
2537
2537
  note: string;
2538
2538
  }[];
2539
2539
  }[];
@@ -2824,7 +2824,7 @@ declare const HybridGraphSchema: z.ZodObject<{
2824
2824
  feature_ids?: string[] | undefined;
2825
2825
  introduces?: {
2826
2826
  concept_id: string;
2827
- aspect?: "new" | "advanced" | undefined;
2827
+ aspect?: "new" | "advanced" | "preview" | undefined;
2828
2828
  note?: string | undefined;
2829
2829
  }[] | undefined;
2830
2830
  }[] | undefined;
package/dist/index.cjs CHANGED
@@ -39,6 +39,22 @@ function validateCurriculumFeed(feed) {
39
39
  }
40
40
  const phaseIds = parsed.phases.map((p) => p.id);
41
41
  if (new Set(phaseIds).size !== phaseIds.length) throw new Error("curriculum feed contains duplicate phase ids");
42
+ if (parsed.phases.length > 0) {
43
+ const phaseIndexOf = /* @__PURE__ */ new Map();
44
+ parsed.phases.forEach((p, i) => {
45
+ for (const id of p.node_ids) phaseIndexOf.set(id, i);
46
+ });
47
+ for (const e of parsed.dependency_edges) {
48
+ const fromIdx = phaseIndexOf.get(e.from);
49
+ const toIdx = phaseIndexOf.get(e.to);
50
+ if (fromIdx === void 0 || toIdx === void 0) continue;
51
+ if (fromIdx > toIdx) {
52
+ throw new Error(
53
+ "curriculum feed teaching-order violation: prerequisite " + e.from + " (phase " + (fromIdx + 1) + ") is scheduled AFTER its consumer " + e.to + " (phase " + (toIdx + 1) + ")"
54
+ );
55
+ }
56
+ }
57
+ }
42
58
  return parsed;
43
59
  }
44
60
  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;
@@ -74,9 +90,10 @@ var init_curriculumFeedSchema = __esm({
74
90
  concept_ref: zod.z.string().nullable().default(null),
75
91
  // Progressive-completion bookkeeping (feed v2, additive):
76
92
  // phase_id = development phase that introduces/uses this node;
77
- // introduce_aspect = how the phase teaches a concept node (first intro vs advanced revisit).
93
+ // introduce_aspect = how the phase teaches a concept node (first intro, advanced
94
+ // revisit, or preview — a light ULO awareness pass that is NOT the introduction).
78
95
  phase_id: zod.z.string().default(""),
79
- introduce_aspect: zod.z.enum(["new", "advanced"]).nullable().default(null)
96
+ introduce_aspect: zod.z.enum(["new", "advanced", "preview"]).nullable().default(null)
80
97
  });
81
98
  exports.FeedEdgeSchema = zod.z.object({
82
99
  from: zod.z.string().min(1),
@@ -123,6 +140,9 @@ function stepMinutes(step) {
123
140
  const effort = step.effort ?? {};
124
141
  return Math.max(0, Math.round(num(effort.estimated_minutes, num(step.estimated_minutes, 45))));
125
142
  }
143
+ function pushWarning(opts, message) {
144
+ (opts.warnings ?? (opts.warnings = [])).push(message);
145
+ }
126
146
  function newKeywordsInOrder(nodes) {
127
147
  const seen = /* @__PURE__ */ new Set();
128
148
  for (const n of nodes) {
@@ -188,13 +208,13 @@ function feedNodeFromConcept(concept) {
188
208
  name: str(concept.name, id),
189
209
  concept_codes: [str(concept.code) || id].filter(Boolean),
190
210
  keywords: { all: strs(concept.keywords), new: [], prerequisite: [] },
191
- estimated_minutes: Math.max(0, Math.round(num(concept.estimated_minutes, 30))),
211
+ estimated_minutes: Math.max(0, Math.round(num(concept.estimated_minutes, 15))),
192
212
  bloom_hint: topBloom,
193
213
  depth_hint: deriveConceptDepthHint(concept),
194
214
  completion_level: "standard",
195
215
  scaffolding: {
196
216
  scaffold_candidates: problemScaffolds,
197
- core_value_minutes: Math.max(0, Math.round(num(concept.estimated_minutes, 30)))
217
+ core_value_minutes: Math.max(0, Math.round(num(concept.estimated_minutes, 15)))
198
218
  },
199
219
  references,
200
220
  feature_id: null,
@@ -259,6 +279,7 @@ function emitFromHybrid(hybrid, opts) {
259
279
  }
260
280
  const emitCtx = [];
261
281
  const emittedConcepts = /* @__PURE__ */ new Set();
282
+ const previewsEmitted = /* @__PURE__ */ new Set();
262
283
  const linkList = arr(hybrid.links);
263
284
  for (const rp of rawPhases) {
264
285
  const pid = str(rp.id);
@@ -267,7 +288,8 @@ function emitFromHybrid(hybrid, opts) {
267
288
  const intros = arr(rp.introduces);
268
289
  const news = intros.filter((i) => str(i.aspect, "new") === "new");
269
290
  const advanced = intros.filter((i) => str(i.aspect) === "advanced");
270
- for (const intro of [...news, ...advanced]) {
291
+ const previews = intros.filter((i) => str(i.aspect) === "preview");
292
+ for (const intro of [...news, ...advanced, ...previews]) {
271
293
  const cid = str(intro.concept_id);
272
294
  const c = conceptById.get(cid);
273
295
  if (!c) {
@@ -276,6 +298,37 @@ function emitFromHybrid(hybrid, opts) {
276
298
  if (str(intro.aspect, "new") === "advanced" && !emittedConcepts.has(cid)) {
277
299
  throw new Error("Phase " + pid + " advanced-revisits concept " + cid + " that was never emitted - fail-closed");
278
300
  }
301
+ if (str(intro.aspect, "new") === "preview") {
302
+ if (emittedConcepts.has(cid)) {
303
+ throw new Error("Phase " + pid + " previews concept " + cid + " that was already introduced - preview must precede the anchor teach - fail-closed");
304
+ }
305
+ if (previewsEmitted.has(cid)) {
306
+ throw new Error("Phase " + pid + " duplicate preview of concept " + cid + " - fail-closed");
307
+ }
308
+ const pidIdx = rawPhases.findIndex((rp2) => str(rp2.id) === pid);
309
+ const anchorIdx = rawPhases.findIndex((rp2) => arr(rp2.introduces).some((i2) => str(i2.concept_id) === cid && str(i2.aspect, "new") !== "preview"));
310
+ if (anchorIdx < 0) {
311
+ throw new Error("Phase " + pid + " previews concept " + cid + " that is never fully introduced in any phase - fail-closed");
312
+ }
313
+ if (pidIdx >= anchorIdx) {
314
+ throw new Error("Phase " + pid + " previews concept " + cid + " whose anchor introduction is not in a LATER phase - fail-closed");
315
+ }
316
+ previewsEmitted.add(cid);
317
+ const prevNode = feedNodeFromConcept(c);
318
+ prevNode.id = cid + "__PREV" + pad22(order);
319
+ prevNode.name = str(c.name, cid) + " (preview)";
320
+ prevNode.phase_id = pid;
321
+ prevNode.introduce_aspect = "preview";
322
+ prevNode.estimated_minutes = Math.max(5, Math.round(prevNode.estimated_minutes * 0.25));
323
+ prevNode.depth_hint = "ulo";
324
+ prevNode.bloom_hint = "Understand";
325
+ prevNode.concept_codes = [str(c.code) || cid];
326
+ prevNode.keywords = { all: strs(c.keywords), new: [], prerequisite: [] };
327
+ prevNode.scaffolding = { scaffold_candidates: [], core_value_minutes: 0 };
328
+ nodes.push(prevNode);
329
+ conceptOrder.push(prevNode.id);
330
+ continue;
331
+ }
279
332
  if (!emittedConcepts.has(cid)) {
280
333
  const node = feedNodeFromConcept(c);
281
334
  node.phase_id = pid;
@@ -331,6 +384,17 @@ function emitFromHybrid(hybrid, opts) {
331
384
  }
332
385
  phaseNodeIds.push(ids);
333
386
  }
387
+ const placed = new Set(phaseNodeIds.flat());
388
+ const orderedNodes = [];
389
+ for (const ids of phaseNodeIds) {
390
+ for (const id of ids) {
391
+ const n = nodes.find((x) => x.id === id);
392
+ if (n) orderedNodes.push(n);
393
+ }
394
+ }
395
+ for (const n of nodes) if (!placed.has(n.id)) orderedNodes.push(n);
396
+ nodes.length = 0;
397
+ nodes.push(...orderedNodes);
334
398
  const edgeSeen = new Set(edges.map((e) => e.from + ">" + e.to));
335
399
  const pushEdge = (from, to, reason) => {
336
400
  const key = from + ">" + to;
@@ -345,6 +409,14 @@ function emitFromHybrid(hybrid, opts) {
345
409
  const firstStep = (stepIdsByFeature.get(fid) || [])[0];
346
410
  if (!firstStep) continue;
347
411
  const depth = str(l.depth, "cio");
412
+ const anchorNode = nodes.find((n2) => n2.kind === "concept" && n2.concept_ref === cid && n2.introduce_aspect === "new");
413
+ const stepNode = nodes.find((n2) => n2.id === firstStep);
414
+ const anchorPhaseIdx = anchorNode ? emitCtx.findIndex((c2) => c2.id === anchorNode.phase_id) : -1;
415
+ const stepPhaseIdx = stepNode ? emitCtx.findIndex((c2) => c2.id === stepNode.phase_id) : -1;
416
+ if (emitCtx.length > 0 && anchorPhaseIdx > stepPhaseIdx) {
417
+ pushWarning(opts, "hybrid link drift: concept " + cid + " is fully taught in a later phase than consumer " + firstStep + " (a preview covers the dependency)");
418
+ continue;
419
+ }
348
420
  pushEdge(cid, firstStep, str(l.relationship, "hybrid link") + " [" + depth + "]");
349
421
  for (const n of nodes) {
350
422
  if (n.kind === "concept" && n.concept_ref === cid && n.introduce_aspect === "advanced" && n.phase_id === str(l.phase_id)) {
@@ -363,6 +435,21 @@ function emitFromHybrid(hybrid, opts) {
363
435
  for (const k of n.keywords?.prerequisite ?? []) {
364
436
  const src = introducer.get(k.toLowerCase());
365
437
  if (!src || src === n.id) continue;
438
+ const srcNode = nodes.find((x) => x.id === src);
439
+ const phaseOf = (id) => {
440
+ const n2 = nodes.find((x) => x.id === id);
441
+ return n2 ? emitCtx.findIndex((c) => c.id === n2.phase_id) : -1;
442
+ };
443
+ const conceptOvertaken = n.kind === "concept" && srcNode?.kind === "product_step";
444
+ const fromPhase = phaseOf(src);
445
+ const toPhase = phaseOf(n.id);
446
+ if (conceptOvertaken || emitCtx.length > 0 && fromPhase > toPhase) {
447
+ pushWarning(
448
+ opts,
449
+ "keyword-order drift: introducer " + src + " sits in a later phase than consumer " + n.id + ' (keyword "' + k + '") \u2014 host should move the introducer earlier'
450
+ );
451
+ continue;
452
+ }
366
453
  if (feedOrder.get(src) < feedOrder.get(n.id)) pushEdge(src, n.id, "keyword prerequisite [" + k + "]");
367
454
  }
368
455
  }
@@ -935,8 +1022,8 @@ var ProjectConceptLinkSchema = zod.z.object({
935
1022
  });
936
1023
  var ConceptIntroductionSchema = zod.z.object({
937
1024
  concept_id: zod.z.string().describe("Concept ID from knowledge_graph"),
938
- aspect: zod.z.enum(["new", "advanced"]).default("new").describe(
939
- "new = concept introduced for the first time; advanced = previously introduced concept revisited at higher depth"
1025
+ aspect: zod.z.enum(["new", "advanced", "preview"]).default("new").describe(
1026
+ "new = concept introduced for the first time; advanced = previously introduced concept revisited at higher depth; preview = light ULO awareness pass ('know it exists') placed at the consuming phase BEFORE the full introduction \u2014 the anchor phase still owns the full 'new' teach"
940
1027
  ),
941
1028
  note: zod.z.string().optional().default("").describe("Which advanced aspect of the concept this phase develops")
942
1029
  });
@@ -2829,8 +2916,8 @@ function resolveProviderChain(config) {
2829
2916
  }
2830
2917
  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 || "");
2831
2918
  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 || "");
2832
- push("nvidia", "https://integrate.api.nvidia.com/v1", process.env.NVIDIA_API_KEY || "", process.env.NVIDIA_MODEL || "nvidia/nemotron-3-ultra-550b-a55b");
2833
- push("openrouter", "https://openrouter.ai/api/v1", process.env.OPENROUTER_API_KEY || "", process.env.OPENROUTER_MODEL || "nvidia/nemotron-3-ultra-550b-a55b");
2919
+ push("nvidia", "https://integrate.api.nvidia.com/v1", process.env.NVIDIA_API_KEY || "", process.env.NVIDIA_MODEL || "nvidia/nemotron-3-ultra-550b-a55b:free");
2920
+ push("openrouter", "https://openrouter.ai/api/v1", process.env.OPENROUTER_API_KEY || "", process.env.OPENROUTER_MODEL || "nvidia/nemotron-3-ultra-550b-a55b:free");
2834
2921
  if (process.env.OPENROUTER_MODEL) {
2835
2922
  const or = chain.find((c) => c.label === "openrouter");
2836
2923
  if (or) {
@@ -4925,6 +5012,8 @@ function auditPhasePlan(plan, features, concepts, links, warnings, log) {
4925
5012
  const phases = [];
4926
5013
  const featurePhase = /* @__PURE__ */ new Map();
4927
5014
  const newIntroPhase = /* @__PURE__ */ new Map();
5015
+ const previewsSeen = /* @__PURE__ */ new Set();
5016
+ const previewPhase = /* @__PURE__ */ new Map();
4928
5017
  const errors = [];
4929
5018
  plan.forEach((rp, idx) => {
4930
5019
  const id = String(rp.id || "PH" + (idx + 1));
@@ -4956,7 +5045,22 @@ function auditPhasePlan(plan, features, concepts, links, warnings, log) {
4956
5045
  errors.push("Phase " + id + ": introduces unknown concept " + cid);
4957
5046
  continue;
4958
5047
  }
4959
- const aspect = String(ri.aspect || "new") === "advanced" ? "advanced" : "new";
5048
+ const rawAspect = String(ri.aspect || "new");
5049
+ const aspect = rawAspect === "advanced" || rawAspect === "preview" ? rawAspect : "new";
5050
+ if (aspect === "preview") {
5051
+ if (newIntroPhase.has(cid)) {
5052
+ errors.push("Phase " + id + ": preview of " + cid + " after its new introduction in phase " + newIntroPhase.get(cid) + " - preview must precede the anchor teach");
5053
+ continue;
5054
+ }
5055
+ if (previewsSeen.has(cid)) {
5056
+ errors.push("Phase " + id + ": duplicate preview of " + cid);
5057
+ continue;
5058
+ }
5059
+ previewsSeen.add(cid);
5060
+ previewPhase.set(cid, order);
5061
+ introductions.push({ concept_id: cid, aspect, note: String(ri.note || "") });
5062
+ continue;
5063
+ }
4960
5064
  if (aspect === "advanced") {
4961
5065
  const first = newIntroPhase.get(cid);
4962
5066
  if (first === void 0) {
@@ -5004,10 +5108,17 @@ function auditPhasePlan(plan, features, concepts, links, warnings, log) {
5004
5108
  }
5005
5109
  for (const [cid, consumerOrder] of firstConsumerPhase) {
5006
5110
  const intro = newIntroPhase.get(cid);
5007
- if (intro === void 0) {
5111
+ const prev = previewPhase.get(cid);
5112
+ if (intro === void 0 && prev === void 0) {
5008
5113
  errors.push("Concept " + cid + " is consumed from phase " + consumerOrder + " but never introduced in any phase");
5009
- } else if (intro > consumerOrder) {
5010
- errors.push("Concept " + cid + " introduced in phase " + intro + " but first consumed in phase " + consumerOrder + " - introduction must come no later than first consumption");
5114
+ } else if (intro !== void 0 && intro > consumerOrder && (prev === void 0 || prev > consumerOrder)) {
5115
+ errors.push("Concept " + cid + " introduced in phase " + intro + " but first consumed in phase " + consumerOrder + " - introduction must come no later than first consumption (or a preview must cover the dependency)");
5116
+ }
5117
+ }
5118
+ for (const [cid, pOrder] of previewPhase) {
5119
+ const anchor = newIntroPhase.get(cid);
5120
+ if (anchor !== void 0 && anchor <= pOrder) {
5121
+ errors.push("Concept " + cid + " previewed in phase " + pOrder + " but fully introduced in phase " + anchor + " - preview must precede the anchor introduction");
5011
5122
  }
5012
5123
  }
5013
5124
  return { phases, errors };
@@ -5023,7 +5134,7 @@ async function decomposePhases(args) {
5023
5134
  const linkList = links.map(
5024
5135
  (l) => "- " + l.feature_id + " -> " + l.concept_id + " (" + l.depth + ")"
5025
5136
  ).join("\n") || "(none)";
5026
- const systemPrompt = "You decompose a software project build into PROGRESSIVE COMPLETION PHASES for learning purposes.\n\nA phase is NOT a feature and NOT a milestone list: it is a cross-feature slice of the\nproduct whose completion yields a DEMONSTRABLY more complete product. Build up gradually:\nthe product grows feature by feature across phases, and each phase adds its features to\nwhat previous phases already built.\n\nRULES:\n1. Every feature must be assigned to EXACTLY ONE phase (no feature reused, none skipped).\n2. Phases are ordered; later phases depend on earlier ones and yield a more complete product.\n3. product_completion describes concretely what the product can DO once the phase is done.\n4. introduces lists which concepts the phase must teach BEFORE its feature work starts:\n - aspect new: first time the concept appears in the course\n - aspect advanced: a concept introduced in an EARLIER phase, revisited here at a\n higher depth (an advanced aspect of the same concept, e.g. validation rules on top\n of basic data modeling). Use the note field to say which aspect.\n5. A concept MUST be introduced (new) no later than the first phase that consumes it.\n6. A concept must not be introduced before its own prerequisites are introduced.\n7. Typically 3-6 phases for a small project; keep phases balanced.\n8. Phase 1 usually covers project setup/foundation features plus the first minimal slice.\n9. Return JSON only.";
5137
+ const systemPrompt = 'You decompose a software project build into PROGRESSIVE COMPLETION PHASES for learning purposes.\n\nA phase is NOT a feature and NOT a milestone list: it is a cross-feature slice of the\nproduct whose completion yields a DEMONSTRABLY more complete product. Build up gradually:\nthe product grows feature by feature across phases, and each phase adds its features to\nwhat previous phases already built.\n\nRULES:\n1. Every feature must be assigned to EXACTLY ONE phase (no feature reused, none skipped).\n2. Phases are ordered; later phases depend on earlier ones and yield a more complete product.\n3. product_completion describes concretely what the product can DO once the phase is done.\n4. introduces lists which concepts the phase must teach BEFORE its feature work starts:\n - aspect new: first time the concept appears in the course\n - aspect advanced: a concept introduced in an EARLIER phase, revisited here at a\n higher depth (an advanced aspect of the same concept, e.g. validation rules on top\n of basic data modeling). Use the note field to say which aspect.\n - aspect preview: a light awareness pass ("know it exists", ULO depth only, ~5 min)\n placed in an EARLIER phase when its feature work needs the vocabulary before the\n full teach. The anchor phase still owns the full new introduction.\n5. A concept MUST be introduced (new) no later than the first phase that consumes it.\n6. A concept must not be introduced before its own prerequisites are introduced.\n7. Typically 3-6 phases for a small project; keep phases balanced.\n8. Phase 1 usually covers project setup/foundation features plus the first minimal slice.\n9. Return JSON only.';
5027
5138
  const baseUserPrompt = "FEATURES:\n" + featureList + "\n\nCONCEPTS:\n" + conceptList + "\n\nFEATURE-CONCEPT LINKS:\n" + linkList + "\n\nReturn JSON with a phases array. Each phase: { id: PH1, name, product_completion, feature_ids: [F0, F1], introduces: [{ concept_id: C1, aspect: new or advanced, note }] }.\n";
5028
5139
  const MAX_DECOMPOSE_ATTEMPTS = 3;
5029
5140
  let auditErrors = [];