@thanh01.pmt/domain-kit 0.6.0 → 0.6.1

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 (87) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -10
  3. package/dist/assembly/index.d.cts +3 -2
  4. package/dist/assembly/index.d.ts +3 -2
  5. package/dist/{chunk-2OGNQUXC.mjs → chunk-4UCXYRM3.mjs} +152 -303
  6. package/dist/chunk-4UCXYRM3.mjs.map +1 -0
  7. package/dist/chunk-7A2SG466.mjs +325 -0
  8. package/dist/chunk-7A2SG466.mjs.map +1 -0
  9. package/dist/{chunk-DRVOV5ZD.mjs → chunk-DSEJLTUO.mjs} +2 -2
  10. package/dist/chunk-DSEJLTUO.mjs.map +1 -0
  11. package/dist/{chunk-ERDTHXQA.mjs → chunk-H4W3YJLK.mjs} +10 -3
  12. package/dist/chunk-H4W3YJLK.mjs.map +1 -0
  13. package/dist/chunk-IBVXXVGE.mjs +14 -0
  14. package/dist/chunk-IBVXXVGE.mjs.map +1 -0
  15. package/dist/{chunk-5OMSYKQP.mjs → chunk-KHCZYLKM.mjs} +33 -18
  16. package/dist/chunk-KHCZYLKM.mjs.map +1 -0
  17. package/dist/{chunk-67GXK3ZE.mjs → chunk-WD3P5V3B.mjs} +3 -2
  18. package/dist/chunk-WD3P5V3B.mjs.map +1 -0
  19. package/dist/{conceptEscalator-DPrs4PwC.d.cts → conceptEscalator-BmMJD8C5.d.cts} +1 -1
  20. package/dist/{conceptEscalator-BCsw0ys_.d.ts → conceptEscalator-DMsehscs.d.ts} +1 -1
  21. package/dist/concepts/index.d.cts +4 -3
  22. package/dist/concepts/index.d.ts +4 -3
  23. package/dist/{cpp-CZUPqJt9.d.cts → cpp-vlDl6pCL.d.cts} +4 -4
  24. package/dist/{cpp-CZUPqJt9.d.ts → cpp-vlDl6pCL.d.ts} +4 -4
  25. package/dist/detector/index.cjs +1 -0
  26. package/dist/detector/index.cjs.map +1 -1
  27. package/dist/detector/index.d.cts +2 -2
  28. package/dist/detector/index.d.ts +2 -2
  29. package/dist/detector/index.mjs +1 -1
  30. package/dist/{domainProfileDetector-DHb1T0zB.d.ts → domainProfileDetector-6pjgL-6_.d.ts} +1 -1
  31. package/dist/{domainProfileDetector-bzugdQmS.d.cts → domainProfileDetector-BsADsuRV.d.cts} +1 -1
  32. package/dist/{domainProfileSchema-CwT3Ffsw.d.cts → domainProfileSchema-779FGk14.d.cts} +38 -1
  33. package/dist/{domainProfileSchema-CwT3Ffsw.d.ts → domainProfileSchema-779FGk14.d.ts} +38 -1
  34. package/dist/extractors/index.d.cts +3 -3
  35. package/dist/extractors/index.d.ts +3 -3
  36. package/dist/feed/index.d.cts +1 -1
  37. package/dist/feed/index.d.ts +1 -1
  38. package/dist/graph/index.cjs +235 -57
  39. package/dist/graph/index.cjs.map +1 -1
  40. package/dist/graph/index.d.cts +9 -6
  41. package/dist/graph/index.d.ts +9 -6
  42. package/dist/graph/index.mjs +3 -1
  43. package/dist/{hybridGraphPipeline-CocfYe4d.d.cts → hybridGraphPipeline-B4mamLim.d.cts} +1 -1
  44. package/dist/{hybridGraphPipeline-9dRKJj7r.d.ts → hybridGraphPipeline-D2_1GUva.d.ts} +1 -1
  45. package/dist/index.cjs +277 -68
  46. package/dist/index.cjs.map +1 -1
  47. package/dist/index.d.cts +9 -8
  48. package/dist/index.d.ts +9 -8
  49. package/dist/index.mjs +11 -9
  50. package/dist/{keywordExtractor-BxlGRMHC.d.cts → keywordExtractor-1YAFntsn.d.cts} +1 -1
  51. package/dist/{keywordExtractor-DU5XRN8-.d.ts → keywordExtractor-DWw8z_AF.d.ts} +1 -1
  52. package/dist/parsers/index.cjs.map +1 -1
  53. package/dist/parsers/index.d.cts +1 -1
  54. package/dist/parsers/index.d.ts +1 -1
  55. package/dist/parsers/index.mjs +1 -1
  56. package/dist/pipeline/index.cjs +261 -68
  57. package/dist/pipeline/index.cjs.map +1 -1
  58. package/dist/pipeline/index.d.cts +10 -9
  59. package/dist/pipeline/index.d.ts +10 -9
  60. package/dist/pipeline/index.mjs +8 -6
  61. package/dist/schemas/index.cjs +8 -0
  62. package/dist/schemas/index.cjs.map +1 -1
  63. package/dist/schemas/index.d.cts +2 -2
  64. package/dist/schemas/index.d.ts +2 -2
  65. package/dist/schemas/index.mjs +1 -1
  66. package/dist/utils/llmClient.cjs +344 -0
  67. package/dist/utils/llmClient.cjs.map +1 -0
  68. package/dist/utils/llmClient.d.cts +118 -0
  69. package/dist/utils/llmClient.d.ts +118 -0
  70. package/dist/utils/llmClient.mjs +4 -0
  71. package/dist/utils/llmClient.mjs.map +1 -0
  72. package/dist/utils/llmTransportResult.cjs +16 -0
  73. package/dist/utils/llmTransportResult.cjs.map +1 -0
  74. package/dist/utils/llmTransportResult.d.cts +36 -0
  75. package/dist/utils/llmTransportResult.d.ts +36 -0
  76. package/dist/utils/llmTransportResult.mjs +3 -0
  77. package/dist/utils/llmTransportResult.mjs.map +1 -0
  78. package/package.json +9 -10
  79. package/dist/chunk-2OGNQUXC.mjs.map +0 -1
  80. package/dist/chunk-5OMSYKQP.mjs.map +0 -1
  81. package/dist/chunk-67GXK3ZE.mjs.map +0 -1
  82. package/dist/chunk-DRVOV5ZD.mjs.map +0 -1
  83. package/dist/chunk-ERDTHXQA.mjs.map +0 -1
  84. package/dist/llmClient-CX5uUiQ1.d.cts +0 -60
  85. package/dist/llmClient-CX5uUiQ1.d.ts +0 -60
  86. package/dist/{curriculumFeedSchema-DYayelAA.d.ts → curriculumFeedSchema-DK-TUAJZ.d.cts} +8 -8
  87. package/dist/{curriculumFeedSchema-DYayelAA.d.cts → curriculumFeedSchema-DK-TUAJZ.d.ts} +8 -8
package/dist/index.cjs CHANGED
@@ -557,6 +557,11 @@ var DetectionConfidenceSchema = zod.z.object({
557
557
  domainCategory: zod.z.number().min(0).max(1).optional().default(0.5),
558
558
  learningMode: zod.z.number().min(0).max(1).optional().default(0.5)
559
559
  });
560
+ var AmbiguousDomainSchema = zod.z.object({
561
+ domain1: DomainCategorySchema,
562
+ domain2: DomainCategorySchema,
563
+ reason: zod.z.string()
564
+ });
560
565
  var DomainProfileSchema = zod.z.object({
561
566
  // Detection results
562
567
  inputType: InputTypeSchema,
@@ -565,6 +570,8 @@ var DomainProfileSchema = zod.z.object({
565
570
  graphType: GraphTypeSchema,
566
571
  // Confidence scores
567
572
  confidence: DetectionConfidenceSchema.optional(),
573
+ // Ambiguity flag when detection is borderline or context-conflicted
574
+ ambiguous: AmbiguousDomainSchema.optional(),
568
575
  // Evidence for detection decisions
569
576
  evidence: zod.z.array(zod.z.string()).optional().default([]),
570
577
  // Detected metadata (optional, populated during detection)
@@ -2503,6 +2510,7 @@ function detectDomainProfile(options) {
2503
2510
  learningMode: modeResult.confidence
2504
2511
  },
2505
2512
  evidence: [...inputResult.evidence, ...domainResult.evidence, ...modeResult.evidence],
2513
+ ambiguous: domainResult.ambiguous,
2506
2514
  detectedTechStack: domainResult.detectedTechStack,
2507
2515
  detectedSubject: options.parsedSyllabus?.subject.title || domainResult.detectedSubject,
2508
2516
  detectedDomain: domainResult.domainCategory,
@@ -2526,6 +2534,17 @@ function determineGraphType(learningMode) {
2526
2534
  }
2527
2535
  }
2528
2536
 
2537
+ // src/utils/llmTransportResult.ts
2538
+ function transportResultFrom(provider, model, options) {
2539
+ return {
2540
+ provider,
2541
+ model,
2542
+ framingStripped: !!options?.framingStripped,
2543
+ upstreamError: options?.upstreamError,
2544
+ usage: options?.usage
2545
+ };
2546
+ }
2547
+
2529
2548
  // src/utils/llmClient.ts
2530
2549
  function summarizeProvenance(callLog) {
2531
2550
  const providers = [];
@@ -2538,6 +2557,44 @@ function summarizeProvenance(callLog) {
2538
2557
  }
2539
2558
  return { providers, models, calls: callLog.length, failed_calls: failed, degraded: failed > 0 };
2540
2559
  }
2560
+ async function responseJsonCompat(response) {
2561
+ const text = await response.text();
2562
+ try {
2563
+ return JSON.parse(text);
2564
+ } catch {
2565
+ const start = text.indexOf("{");
2566
+ if (start < 0) {
2567
+ throw new LlmClientError(`LLM API returned a non-JSON body: ${text.slice(0, 200)}`);
2568
+ }
2569
+ let depth = 0;
2570
+ let inString = false;
2571
+ let escape = false;
2572
+ for (let i = start; i < text.length; i++) {
2573
+ const ch = text[i];
2574
+ if (escape) {
2575
+ escape = false;
2576
+ continue;
2577
+ }
2578
+ if (ch === "\\") {
2579
+ escape = true;
2580
+ continue;
2581
+ }
2582
+ if (ch === '"') {
2583
+ inString = !inString;
2584
+ continue;
2585
+ }
2586
+ if (inString) continue;
2587
+ if (ch === "{") depth++;
2588
+ if (ch === "}") {
2589
+ depth--;
2590
+ if (depth === 0) {
2591
+ return JSON.parse(text.slice(start, i + 1));
2592
+ }
2593
+ }
2594
+ }
2595
+ throw new LlmClientError(`LLM API returned an unterminated JSON body: ${text.slice(0, 200)}`);
2596
+ }
2597
+ }
2541
2598
  var LlmClientError = class extends Error {
2542
2599
  constructor(message, status, cause) {
2543
2600
  super(message);
@@ -2546,28 +2603,22 @@ var LlmClientError = class extends Error {
2546
2603
  this.name = "LlmClientError";
2547
2604
  }
2548
2605
  };
2549
- function resolveProviderChain(config) {
2550
- const chain = [];
2551
- const push = (label, baseUrl, apiKey, model) => {
2552
- if (apiKey && baseUrl && model && !chain.some((c) => c.baseUrl === baseUrl && c.model === model)) {
2553
- chain.push({ label, baseUrl, apiKey, model });
2554
- }
2555
- };
2556
- if (config?.apiKey) {
2557
- push("config", config.baseUrl || "https://api.openai.com/v1", config.apiKey, config.model || "gpt-4o-mini");
2558
- }
2559
- 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 || "");
2560
- 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 || "");
2561
- push("nvidia", "https://integrate.api.nvidia.com/v1", process.env.NVIDIA_API_KEY || "", process.env.NVIDIA_MODEL || "nvidia/nemotron-3-ultra-550b-a55b:free");
2562
- push("openrouter", "https://openrouter.ai/api/v1", process.env.OPENROUTER_API_KEY || "", process.env.OPENROUTER_MODEL || "nvidia/nemotron-3-ultra-550b-a55b:free");
2563
- if (process.env.OPENROUTER_MODEL) {
2564
- const or = chain.find((c) => c.label === "openrouter");
2565
- if (or) {
2566
- const rest = chain.filter((c) => c !== or);
2567
- return [or, ...rest];
2568
- }
2569
- }
2570
- return chain;
2606
+ function build9routerProvider(model) {
2607
+ const apiKey = process.env.NINEROUTER_API_KEY;
2608
+ if (!apiKey) return null;
2609
+ const baseUrl = (process.env.NINEROUTER_BASE_URL || "https://ai-router.orchable.app/v1").replace(/^["']|["']$/g, "").trim();
2610
+ const label = "9router";
2611
+ const modelName = (model || process.env.NINEROUTER_MODEL || "laguna-s-2.1").trim();
2612
+ return { label, baseUrl, apiKey, model: modelName };
2613
+ }
2614
+ function resolve9routerChain(configModel) {
2615
+ const primary = build9routerProvider(configModel);
2616
+ if (!primary) return [];
2617
+ const alt = process.env.NINEROUTER_MODEL_ALT && process.env.NINEROUTER_MODEL_ALT.trim();
2618
+ if (!alt) return [primary];
2619
+ const secondary = build9routerProvider(alt);
2620
+ if (!secondary) return [primary];
2621
+ return primary.model === secondary.model ? [primary] : [primary, secondary];
2571
2622
  }
2572
2623
  var MIN_REQUEST_INTERVAL_MS = 2500;
2573
2624
  var requestIntervalMs = Number(process.env.LLM_MIN_REQUEST_INTERVAL_MS || MIN_REQUEST_INTERVAL_MS);
@@ -2578,11 +2629,11 @@ async function pacedDelay() {
2578
2629
  lastRequestAt = Date.now();
2579
2630
  }
2580
2631
  function createLlmClient(config) {
2581
- const chain = resolveProviderChain(config);
2632
+ const chain = resolve9routerChain(config?.model);
2582
2633
  const primary = chain[0];
2583
2634
  if (!primary) {
2584
2635
  throw new LlmClientError(
2585
- "No LLM provider configured. Set NVIDIA_API_KEY (primary) and/or OPENROUTER_API_KEY (fallback), or pass llmConfig."
2636
+ "No 9router provider configured. Set NINEROUTER_API_KEY and NINEROUTER_MODEL (or the NINEROUTER_* env defaults expected by the kit)."
2586
2637
  );
2587
2638
  }
2588
2639
  primary.apiKey;
@@ -2664,17 +2715,22 @@ function createLlmClient(config) {
2664
2715
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
2665
2716
  });
2666
2717
  if (!retry.ok) throw err;
2667
- const retryData = await retry.json();
2718
+ const retryData = await responseJsonCompat(retry);
2668
2719
  const retryContent2 = retryData.choices?.[0]?.message?.content || "";
2669
2720
  if (!retryContent2.trim()) throw err;
2670
2721
  callLog[recIdx] = { ...callLog[recIdx], ok: true };
2722
+ const transport2 = transportResultFrom(provider.label, retryData.model || provider.model, {
2723
+ usage: retryData.usage,
2724
+ framingStripped: true
2725
+ });
2671
2726
  return {
2672
2727
  content: retryContent2,
2673
2728
  model: retryData.model || provider.model,
2674
2729
  provider: provider.label,
2675
2730
  attempts: attempts.slice(),
2676
2731
  usage: retryData.usage,
2677
- finishReason: retryData.choices?.[0]?.finish_reason
2732
+ finishReason: retryData.choices?.[0]?.finish_reason,
2733
+ _transport: transport2
2678
2734
  };
2679
2735
  }
2680
2736
  if (TRANSIENT.has(response.status)) {
@@ -2694,21 +2750,26 @@ function createLlmClient(config) {
2694
2750
  })
2695
2751
  });
2696
2752
  if (!retry.ok) throw err;
2697
- const retryData = await retry.json();
2753
+ const retryData = await responseJsonCompat(retry);
2698
2754
  const retryContent = retryData.choices?.[0]?.message?.content || "";
2699
2755
  callLog[recIdx] = { ...callLog[recIdx], ok: true };
2756
+ const transport2 = transportResultFrom(provider.label, retryData.model || provider.model, {
2757
+ usage: retryData.usage,
2758
+ framingStripped: true
2759
+ });
2700
2760
  return {
2701
2761
  content: retryContent,
2702
2762
  model: retryData.model || provider.model,
2703
2763
  provider: provider.label,
2704
2764
  attempts: attempts.slice(),
2705
2765
  usage: retryData.usage,
2706
- finishReason: retryData.choices?.[0]?.finish_reason
2766
+ finishReason: retryData.choices?.[0]?.finish_reason,
2767
+ _transport: transport2
2707
2768
  };
2708
2769
  }
2709
2770
  throw err;
2710
2771
  }
2711
- const data = await response.json();
2772
+ const data = await responseJsonCompat(response);
2712
2773
  const wrappedError = data.error;
2713
2774
  if (wrappedError) {
2714
2775
  throw new LlmClientError("[" + provider.label + "] upstream error " + (wrappedError.code ?? "") + ": " + (wrappedError.message ?? "unknown"));
@@ -2717,13 +2778,18 @@ function createLlmClient(config) {
2717
2778
  if (!content.trim()) {
2718
2779
  throw new LlmClientError("[" + provider.label + "] empty completion returned");
2719
2780
  }
2781
+ const transport = transportResultFrom(provider.label, data.model || provider.model, {
2782
+ usage: data.usage,
2783
+ framingStripped: true
2784
+ });
2720
2785
  return {
2721
2786
  content,
2722
2787
  model: data.model || provider.model,
2723
2788
  provider: provider.label,
2724
2789
  attempts: attempts.slice(),
2725
2790
  usage: data.usage,
2726
- finishReason: data.choices?.[0]?.finish_reason
2791
+ finishReason: data.choices?.[0]?.finish_reason,
2792
+ _transport: transport
2727
2793
  };
2728
2794
  } catch (err) {
2729
2795
  const msg = err instanceof Error ? err.message : String(err);
@@ -2897,7 +2963,7 @@ async function extractProjectOverview(options) {
2897
2963
  (kw) => `- ${kw.keyword} (from ${kw.source})`
2898
2964
  );
2899
2965
  if (kwItems.length > 0) {
2900
- keywordsAnchor = "\nAST Domain Keywords & Patterns (from static analysis \u2014 use as context anchor):\n" + kwItems.join("\n") + "\n";
2966
+ keywordsAnchor = "\nDomain Keywords & Patterns (from regex-based static scan \u2014 use as context anchor):\n" + kwItems.join("\n") + "\n";
2901
2967
  }
2902
2968
  }
2903
2969
  const systemPrompt = "You are a software architect. The app the user will build IS EXACTLY the provided files (not an SDK/lib). Analyze them and return a PROJECT OVERVIEW \u2014 meta only, DO NOT list steps (steps are handled in a later step). NEVER invent files/APIs. NEVER reference internal SDK/lib unless it appears in the given files.\nEND-USER VIEW (M1/Design Thinking): product.purpose must describe the USER PROBLEM (what the user does with the app, which need it solves). NEVER describe repo/artifacts.\nDEVELOPMENT STAGES: Include 4-6 development stages in product.development_stages. Each stage must have: stage (name), need (list of feature needs), learn (concepts learned), product_state (what works), temporary_approach, and validation.\nMISSING GAPS: Identify 0-5 missing_gaps (features/functionality that should exist but don't).\nTECH DEBT: Identify 0-5 tech_debt issues (code quality problems).\nALL text must be in ENGLISH.";
@@ -3722,7 +3788,110 @@ function techSignalHits(text) {
3722
3788
  }
3723
3789
  return hits;
3724
3790
  }
3725
- function auditDepthLayers(concepts) {
3791
+ var COMPLETABLE_VERBS = [
3792
+ "build",
3793
+ "create",
3794
+ "implement",
3795
+ "write",
3796
+ "configure",
3797
+ "add",
3798
+ "use",
3799
+ "apply",
3800
+ "display",
3801
+ "store",
3802
+ "load",
3803
+ "save",
3804
+ "handle",
3805
+ "parse",
3806
+ "validate",
3807
+ "test",
3808
+ "register",
3809
+ "compute",
3810
+ "generate",
3811
+ "print",
3812
+ "format",
3813
+ "convert",
3814
+ "extend",
3815
+ "define",
3816
+ "execute",
3817
+ "run",
3818
+ "read",
3819
+ "fetch",
3820
+ "wrap",
3821
+ "log",
3822
+ "call",
3823
+ "raise",
3824
+ "install",
3825
+ "import",
3826
+ "export",
3827
+ "declare",
3828
+ "embed",
3829
+ "model",
3830
+ "represent",
3831
+ "override",
3832
+ "customize",
3833
+ "enforce",
3834
+ "mock",
3835
+ "assert",
3836
+ "query",
3837
+ "index",
3838
+ "cache",
3839
+ "stream",
3840
+ "split",
3841
+ "join",
3842
+ "strip",
3843
+ "encode",
3844
+ "decode",
3845
+ "traverse",
3846
+ "measure",
3847
+ "evaluate",
3848
+ "benchmark",
3849
+ "diagnose",
3850
+ "demonstrate",
3851
+ "verify",
3852
+ "organize",
3853
+ "structure",
3854
+ "choose",
3855
+ "select",
3856
+ "compare",
3857
+ "distinguish",
3858
+ "attach",
3859
+ "annotate",
3860
+ "expose",
3861
+ "accept",
3862
+ "manage",
3863
+ "group",
3864
+ "sort",
3865
+ "filter",
3866
+ "map",
3867
+ "reduce",
3868
+ "catch",
3869
+ "throw",
3870
+ "migrate",
3871
+ "inspect",
3872
+ "trace",
3873
+ "parameterize",
3874
+ "construct",
3875
+ "design",
3876
+ "capture",
3877
+ // Completable implementation verbs present in production SIO output but
3878
+ // missing from the original registry (2026-10-07 smoke test, swift kg):
3879
+ "update",
3880
+ "inject",
3881
+ "compose",
3882
+ "pass",
3883
+ "modify",
3884
+ "connect",
3885
+ "remove",
3886
+ "transform",
3887
+ "trigger",
3888
+ "refresh",
3889
+ "persist",
3890
+ "observe",
3891
+ "arrange",
3892
+ "derive"
3893
+ ];
3894
+ function auditDepthLayers(concepts, transport) {
3726
3895
  return concepts.map((c) => {
3727
3896
  const ulo = (c.ulo ?? "").trim();
3728
3897
  const cio = (c.cio ?? "").trim();
@@ -3743,7 +3912,7 @@ function auditDepthLayers(concepts) {
3743
3912
  });
3744
3913
  const conceptKeywords = (c.keywords ?? []).map((k) => k.toLowerCase()).filter(Boolean);
3745
3914
  const sioOffConceptItems = conceptKeywords.length > 0 ? sioItems.filter((s) => !boundToConcept(s)) : [];
3746
- const COMPLETABLE = /\b(build|create|implement|write|configure|add|use|apply|display|store|load|save|handle|parse|validate|test|register|compute|generate|print|format|convert|extend|define|execute|run|read|fetch|wrap|log|call|raise|install|import|export|declare|embed|model|represent|override|customize|enforce|mock|assert|query|index|cache|stream|split|join|strip|encode|decode|traverse|measure|evaluate|benchmark|diagnose|demonstrate|verify|organize|structure|choose|select|compare|distinguish|attach|annotate|expose|accept|manage|group|sort|filter|map|reduce|catch|throw|migrate|inspect|trace|parameterize|construct|design|capture)\b/i;
3915
+ const COMPLETABLE = new RegExp("\\b(?:" + COMPLETABLE_VERBS.join("|") + ")\\b", "i");
3747
3916
  const sioNotCompletable = sioItems.filter((s) => !COMPLETABLE.test(s) && !/`[^`]+`/.test(s));
3748
3917
  const density = (text) => {
3749
3918
  if (!text) return 0;
@@ -3783,7 +3952,8 @@ function auditDepthLayers(concepts) {
3783
3952
  clauseOk,
3784
3953
  codesOk,
3785
3954
  codeIssues,
3786
- passed
3955
+ passed,
3956
+ transport
3787
3957
  };
3788
3958
  });
3789
3959
  }
@@ -3793,14 +3963,14 @@ function excerpt(text, max) {
3793
3963
  const t = (text || "").trim();
3794
3964
  return t.length > max ? t.slice(0, max) + "..." : t;
3795
3965
  }
3796
- async function chatJson(client, system, user) {
3966
+ async function chatJson(client, system, user, transport) {
3797
3967
  const r = await client.chat(
3798
3968
  [{ role: "system", content: system }, { role: "user", content: user }],
3799
3969
  { temperature: 0.1, maxTokens: parseInt(process.env.KG_MAX_TOKENS || "65536", 10) }
3800
3970
  );
3801
3971
  return parseJsonFromLlm(r.content);
3802
3972
  }
3803
- var LO_BATCH_SIZE = 6;
3973
+ var LO_BATCH_SIZE = 10;
3804
3974
  function chunkConcepts(items, size) {
3805
3975
  const out = [];
3806
3976
  for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
@@ -3839,7 +4009,7 @@ async function authorDepthLayersHierarchically(client, concepts, techStack, warn
3839
4009
  for (const batch of chunkConcepts(concepts, LO_BATCH_SIZE)) {
3840
4010
  const batchList = batch.map((c) => "- " + c.id + " (" + c.name + ")" + (c.description ? ": " + excerpt(c.description, 160) : "")).join("\n");
3841
4011
  const batchUser = "Concepts:\n" + batchList + '\n\nReturn JSON:\n{"ulos":[{"concept_id":"C1","ulo":"..."}]}';
3842
- const uloRes = await chatJson(client, uloSystem, batchUser);
4012
+ const uloRes = await chatJson(client, uloSystem, batchUser, transportResultFrom("9router", client.model, { framingStripped: true }));
3843
4013
  for (const u of uloRes.ulos ?? []) {
3844
4014
  if (u.concept_id && u.ulo) uloByConcept[u.concept_id] = standardStatement(u.ulo);
3845
4015
  }
@@ -3878,7 +4048,7 @@ async function authorDepthLayersHierarchically(client, concepts, techStack, warn
3878
4048
  new Set(batch.map((c) => c.id));
3879
4049
  const batchUloContext = batch.map((c) => "- " + c.id + " (" + c.name + "): " + (uloByConcept[c.id] || "(missing)")).join("\n");
3880
4050
  const batchCioUser = "Approved ULOs:\n" + batchUloContext + '\n\nReturn JSON:\n{"cios":[{"concept_id":"C1","cio":"..."}]}';
3881
- const cioRes = await chatJson(client, cioSystem, batchCioUser);
4051
+ const cioRes = await chatJson(client, cioSystem, batchCioUser, transportResultFrom("9router", client.model, { framingStripped: true }));
3882
4052
  for (const x of cioRes.cios ?? []) {
3883
4053
  if (x.concept_id && x.cio) cioByConcept[x.concept_id] = standardStatement(x.cio);
3884
4054
  }
@@ -3903,15 +4073,7 @@ async function authorDepthLayersHierarchically(client, concepts, techStack, warn
3903
4073
  " context where it is used. Application/Analysis/Creation skills are assessed",
3904
4074
  " INDIRECTLY through completing these tasks \u2014 there is no fixed Bloom ceiling.",
3905
4075
  " MACHINE CHECK: every item MUST contain at least one ACTION VERB from this whitelist",
3906
- " (build, create, implement, write, configure, add, use, apply, display, store, load,",
3907
- " save, handle, parse, validate, test, register, compute, generate, print, format,",
3908
- " convert, extend, define, execute, run, read, fetch, wrap, log, call, raise, install,",
3909
- " import, export, declare, embed, model, represent, override, customize, enforce, mock,",
3910
- " assert, query, index, cache, stream, split, join, strip, encode, decode, traverse,",
3911
- " measure, evaluate, benchmark, diagnose, demonstrate, verify, organize, structure,",
3912
- " choose, select, compare, distinguish, attach, annotate, expose, accept, manage, group,",
3913
- " sort, filter, map, reduce, catch, throw, migrate, inspect, trace, parameterize,",
3914
- " construct, design, capture) \u2014 OR wrap the concrete construct in backticks.",
4076
+ " (" + COMPLETABLE_VERBS.join(", ") + ") \u2014 OR wrap the concrete construct in backticks.",
3915
4077
  " Items with abstract verbs (understand, learn, know, grasp, appreciate, explore) and",
3916
4078
  " no backtick construct FAIL the audit. Prefer starting the task phrase with the verb.",
3917
4079
  "3. 2-3 items per concept, each referencing a DIFFERENT concrete construct.",
@@ -3939,7 +4101,7 @@ async function authorDepthLayersHierarchically(client, concepts, techStack, warn
3939
4101
  "Return JSON:",
3940
4102
  '{"sios":[{"concept_id":"C1","sio":["...","..."]}]}'
3941
4103
  ].join("\n");
3942
- const sioRes = await chatJson(client, sioSystem, batchSioUser);
4104
+ const sioRes = await chatJson(client, sioSystem, batchSioUser, transportResultFrom("9router", client.model, { framingStripped: true }));
3943
4105
  for (const x of sioRes.sios ?? []) {
3944
4106
  if (x.concept_id && Array.isArray(x.sio)) sioByConcept[x.concept_id] = x.sio.map((item) => standardStatement(String(item)));
3945
4107
  }
@@ -3992,7 +4154,7 @@ async function authorDepthLayersHierarchically(client, concepts, techStack, warn
3992
4154
  "- CIO: HOW mechanism, technology-agnostic, more concrete than ULO, Marr 2-Language neutral.",
3993
4155
  "- SIO: keyword-bound completable implementation tasks naming real constructs of " + techStack + ".",
3994
4156
  "- BINDING RULE: every SIO item must contain at least one of the concept's listed keywords VERBATIM (copy the exact keyword text into the sentence).",
3995
- "- COMPLETABILITY RULE: every SIO item must contain an actionable verb (build, create, implement, write, configure, parse, validate, handle, store, load, run, read, fetch, display, generate, format, convert, test, debug, trace, measure, design, structure, select, filter, sort, map, reduce, catch, throw, migrate, deploy, query, index, cache, stream, inspect) or wrap a concrete construct in backticks \u2014 abstract verbs (understand, learn, know, grasp, explore, appreciate) always fail.",
4157
+ "- COMPLETABILITY RULE: every SIO item must contain an actionable verb (" + ["build", "create", "update", "inject", "compose", "pass", "modify", "use"].join(", ") + " ...full registry in the SIO authoring step) or wrap a concrete construct in backticks \u2014 abstract verbs (understand, learn, know, grasp, explore, appreciate) always fail.",
3996
4158
  "",
3997
4159
  JSON_ONLY
3998
4160
  ].join("\n");
@@ -4040,7 +4202,7 @@ async function authorDepthLayersHierarchically(client, concepts, techStack, warn
4040
4202
  "Depth-layer contract audit failed after repair for: " + detail + " \u2014 regenerate or fix the prompt contract (fail-closed, no silent fallback)"
4041
4203
  );
4042
4204
  }
4043
- return { uloByConcept, cioByConcept, sioByConcept, audits, warnings };
4205
+ return { uloByConcept, cioByConcept, sioByConcept, audits, warnings, lastTransport: audits[0]?.transport };
4044
4206
  }
4045
4207
  function cell(v) {
4046
4208
  return (v || "").split(",").map((s) => s.trim()).filter(Boolean);
@@ -4183,7 +4345,7 @@ async function standardizeConcepts(opts) {
4183
4345
  " Adopt the tree's terminology. Only propose new when the tree genuinely has NO concept teaching that knowledge (broader AND narrower both fail).",
4184
4346
  ' 2. "proposed_new" - no tree concept covers it at the same granularity. Propose an UPPER_SNAKE_CASE code (descriptive, stable, max 5 words) and a one-sentence definition.',
4185
4347
  " If between 1 and 3 tree concepts are CLOSE but not exact, list their codes in possible_tree_matches for human review.",
4186
- `For a "standardized" decision ALSO return "adopted_keywords": the subset of that tree concept's keywords (from the shortlist) that make sense for the course TECH STACK. Drop keywords that name tooling from other ecosystems (e.g. JS test runners for a Python course) \u2014 keep conceptual keywords even when spelled generically. Maximum 8, ordered most-relevant first.`,
4348
+ `For a "standardized" decision ALSO return "adopted_keywords": copy VERBATIM from that tree concept's keyword list in the shortlist (exact tokens, do not re-spell, singularize or translate). Pick the subset that makes sense for the course TECH STACK; if none fit, return an EMPTY array \u2014 never invent a keyword. Maximum 8, ordered most-relevant first.`,
4187
4349
  "Rules:",
4188
4350
  "- Judge by MEANING (definition + keywords), not by string similarity.",
4189
4351
  '- COVERAGE RULE: a "standardized" match must cover ALL primary dimensions of the input concept \u2014 every distinct aspect named in its title or description. A tree concept covering only ONE of several aspects (e.g. "interpreting error messages" for a concept about error handling AND user experience) is a PARTIAL match: decide "proposed_new" and list that tree code in possible_tree_matches.',
@@ -4223,10 +4385,25 @@ async function standardizeConcepts(opts) {
4223
4385
  const tc = catalog.concepts.find((x) => x.code === d.tree_code);
4224
4386
  if (!tc) throw new Error("Concept " + c.id + " standardized to unknown tree code " + d.tree_code + " \u2014 fail-closed");
4225
4387
  const treeKw = new Set(tc.keywords.map((k) => k.toLowerCase()));
4226
- const adopted = (d.adopted_keywords || []).map((k) => String(k)).filter((k) => {
4227
- if (treeKw.has(k.toLowerCase())) return true;
4228
- throw new Error("Concept " + c.id + ' adopted keyword "' + k + '" not in tree concept ' + tc.code + " \u2014 fail-closed");
4229
- });
4388
+ const rawAdopted = (d.adopted_keywords || []).map((k) => String(k)).filter(Boolean);
4389
+ let adopted;
4390
+ if (tc.keywords.length === 0) {
4391
+ adopted = rawAdopted.filter((k) => (c.keywords ?? []).some((ck) => ck.toLowerCase() === k.toLowerCase()));
4392
+ if (adopted.length === 0) adopted = (c.keywords ?? []).slice(0, 8);
4393
+ warnings.push("Concept " + c.id + ": tree concept " + tc.code + " has no keyword registry \u2014 adopted " + adopted.length + " keyword(s) from the analysis concept");
4394
+ } else {
4395
+ const norm = (k) => k.toLowerCase().replace(/ies$/, "y").replace(/s$/, "");
4396
+ const treeKwNorm = new Set([...treeKw].map(norm));
4397
+ const dropped = [];
4398
+ adopted = rawAdopted.filter((k) => {
4399
+ if (treeKw.has(k.toLowerCase()) || treeKwNorm.has(norm(k))) return true;
4400
+ dropped.push(k);
4401
+ return false;
4402
+ });
4403
+ if (dropped.length > 0) {
4404
+ warnings.push("Concept " + c.id + ": dropped " + dropped.length + " adopted keyword(s) not in tree concept " + tc.code + " registry: " + dropped.join(", "));
4405
+ }
4406
+ }
4230
4407
  if (tc.keywords.length > 0 && adopted.length === 0) {
4231
4408
  throw new Error("Concept " + c.id + " standardized to " + tc.code + " without adopted_keywords \u2014 fail-closed");
4232
4409
  }
@@ -4321,7 +4498,7 @@ async function generateKnowledgeGraph(options) {
4321
4498
  log("STEP_1", `Parsed syllabus: ${parsedSyllabus.units.length} units, ${parsedSyllabus.allTopics.length} topics`);
4322
4499
  }
4323
4500
  const client = createLlmClient(llmConfig);
4324
- const maxSyllabusChars = parseInt(process.env.KG_MAX_SYLLABUS_CHARS || "16000", 10);
4501
+ const maxSyllabusChars = parseInt(process.env.KG_MAX_SYLLABUS_CHARS || "32000", 10);
4325
4502
  let truncatedSyllabus = "";
4326
4503
  if (syllabusText) {
4327
4504
  if (syllabusText.length > maxSyllabusChars) {
@@ -4511,23 +4688,32 @@ Return JSON:
4511
4688
  }
4512
4689
  }
4513
4690
  const idByTreeCode = new Map(concepts.filter((c) => c.tree_code).map((c) => [c.tree_code, c.id]));
4691
+ const unmappedTreePrereqs = /* @__PURE__ */ new Set();
4514
4692
  for (const c of concepts) {
4515
4693
  for (const tp of c.tree_prerequisites || []) {
4516
4694
  const target = idByTreeCode.get(tp);
4517
4695
  if (target && target !== c.id && !(c.prerequisites || []).includes(target)) {
4518
4696
  c.prerequisites = [...c.prerequisites || [], target];
4519
4697
  warnings.push("Prereq seeded from tree: " + c.id + " <- " + tp + " (" + target + ")");
4698
+ } else if (!target) {
4699
+ unmappedTreePrereqs.add(tp);
4520
4700
  }
4521
4701
  }
4522
4702
  }
4703
+ if (unmappedTreePrereqs.size > 0) {
4704
+ const shown = [...unmappedTreePrereqs].slice(0, 10).join(", ");
4705
+ warnings.push("Tree prereqs outside THIS analysis (no edge seeded, " + unmappedTreePrereqs.size + " codes): " + shown + (unmappedTreePrereqs.size > 10 ? ", ..." : ""));
4706
+ }
4523
4707
  log("STEP_2_5", "Standardized: " + std.decisions.filter((d) => d.decision === "standardized").length + ", proposed new: " + std.decisions.filter((d) => d.decision === "proposed_new").length);
4708
+ } else {
4709
+ warnings.push("No knowledgeTreePath provided \u2014 concepts are NOT standardized against the knowledge tree (all proposed_new)");
4524
4710
  }
4525
4711
  log("STEP_3", "Checking for prerequisite cycles...");
4526
4712
  const cycles = detectPrerequisiteCycles(concepts);
4527
4713
  if (cycles.length > 0) {
4528
4714
  log("STEP_3", `Found ${cycles.length} cycle(s) in prerequisite chain \u2014 breaking cycles`);
4529
4715
  for (const cycle of cycles) {
4530
- warnings.push(`Cycle detected: ${cycle.join(" \u2192 ")} \u2014 breaking at weakest edge`);
4716
+ warnings.push(`Cycle detected: ${cycle.join(" \u2192 ")} \u2014 removing the closing back-edge (last DFS edge; pedagogically arbitrary)`);
4531
4717
  }
4532
4718
  const { fixed, removed } = breakPrerequisiteCycles(concepts, cycles);
4533
4719
  for (let i = 0; i < concepts.length; i++) {
@@ -6120,7 +6306,7 @@ async function runProjectGraphPipeline(options) {
6120
6306
  const filePaths = selectedPaths;
6121
6307
  const primaryLang = detectPrimaryLanguage(filePaths);
6122
6308
  log("STEP_1", `Primary language: ${primaryLang}, ${filePaths.length} files loaded (${fileContentsMap.size} with content)`);
6123
- log("STEP_1", "Parsing source files with AST-level analysis...");
6309
+ log("STEP_1", "Parsing source files with regex-based scan (no AST library)...");
6124
6310
  const allParseResults = [];
6125
6311
  for (const [relPath, content] of fileContentsMap) {
6126
6312
  const fullPath = relPath;
@@ -6144,7 +6330,7 @@ async function runProjectGraphPipeline(options) {
6144
6330
  } else {
6145
6331
  mergedContext = mergeCppResults(allParseResults.filter((r) => "error_handling" in r && !("docstrings" in r) && !("property_wrappers" in r)));
6146
6332
  }
6147
- log("STEP_2", "Extracting keywords from AST analysis...");
6333
+ log("STEP_2", "Extracting keywords from regex-based source scan...");
6148
6334
  const astKeywords = extractKeywords(mergedContext);
6149
6335
  log("STEP_2", `Extracted ${astKeywords.length} keywords`);
6150
6336
  log("STEP_3", "Building SDK API index...");
@@ -6371,6 +6557,15 @@ async function runProjectGraphPipeline(options) {
6371
6557
  };
6372
6558
  }
6373
6559
 
6560
+ // src/pipeline/emitFeed.ts
6561
+ function emitNormalizedFeed(req) {
6562
+ return emitCurriculumFeed(req.graph, {
6563
+ warnings: req.warnings,
6564
+ hallucinationCount: req.hallucinationCount,
6565
+ provenance: req.provenance ? summarizeProvenance(req.provenance) : void 0
6566
+ });
6567
+ }
6568
+
6374
6569
  // src/pipeline/knowledgeGraphPipeline.ts
6375
6570
  async function runKnowledgeGraphPipeline(options) {
6376
6571
  const warnings = [];
@@ -6391,9 +6586,10 @@ async function runKnowledgeGraphPipeline(options) {
6391
6586
  parsedSyllabus
6392
6587
  });
6393
6588
  warnings.push(...result.warnings);
6394
- const curriculumFeed = emitCurriculumFeed(result.knowledgeGraph, {
6589
+ const curriculumFeed = emitNormalizedFeed({
6590
+ graph: result.knowledgeGraph,
6395
6591
  warnings,
6396
- provenance: summarizeProvenance(result.provenance)
6592
+ provenance: result.provenance
6397
6593
  });
6398
6594
  return {
6399
6595
  knowledgeGraph: result.knowledgeGraph,
@@ -6409,9 +6605,10 @@ async function runHybridGraphPipeline(options) {
6409
6605
  const warnings = [];
6410
6606
  const result = await generateHybridGraph(options);
6411
6607
  warnings.push(...result.warnings);
6412
- const curriculumFeed = emitCurriculumFeed(result.hybridGraph, {
6608
+ const curriculumFeed = emitNormalizedFeed({
6609
+ graph: result.hybridGraph,
6413
6610
  warnings,
6414
- provenance: summarizeProvenance(result.provenance)
6611
+ provenance: result.provenance
6415
6612
  });
6416
6613
  return {
6417
6614
  hybridGraph: result.hybridGraph,
@@ -6464,7 +6661,7 @@ async function runDomainPipeline(options) {
6464
6661
  const projectResult2 = await runProjectGraphPipeline({
6465
6662
  repoDir: options.repoDir,
6466
6663
  goal: options.goal || "Build complete project",
6467
- techStack: options.techStack || "typescript",
6664
+ techStack: options.techStack || domainProfile.detectedTechStack?.[0] || "typescript",
6468
6665
  llmConfig: options.llmConfig,
6469
6666
  embeddings: options.embeddings,
6470
6667
  onProgress: options.onProgress
@@ -6474,8 +6671,9 @@ async function runDomainPipeline(options) {
6474
6671
  description: options.description,
6475
6672
  syllabusText: options.syllabusText,
6476
6673
  parsedSyllabus: options.parsedSyllabus,
6674
+ gradeBand: options.gradeBand,
6477
6675
  domain: domainProfile.domainCategory,
6478
- techStack: options.techStack,
6676
+ techStack: options.techStack || domainProfile.detectedTechStack?.[0],
6479
6677
  llmConfig: options.llmConfig,
6480
6678
  onProgress: options.onProgress,
6481
6679
  knowledgeTreePath: options.knowledgeTreePath,
@@ -6505,8 +6703,9 @@ async function runDomainPipeline(options) {
6505
6703
  description: options.description,
6506
6704
  syllabusText: options.syllabusText,
6507
6705
  parsedSyllabus: options.parsedSyllabus,
6706
+ gradeBand: options.gradeBand,
6508
6707
  domain: domainProfile.domainCategory,
6509
- techStack: options.techStack,
6708
+ techStack: options.techStack || domainProfile.detectedTechStack?.[0],
6510
6709
  llmConfig: options.llmConfig,
6511
6710
  onProgress: options.onProgress,
6512
6711
  knowledgeTreePath: options.knowledgeTreePath,
@@ -6535,10 +6734,11 @@ async function runDomainPipeline(options) {
6535
6734
  embeddings: options.embeddings,
6536
6735
  onProgress: options.onProgress
6537
6736
  });
6538
- const curriculumFeed = emitCurriculumFeed(projectResult.projectGraph, {
6737
+ const curriculumFeed = emitNormalizedFeed({
6738
+ graph: projectResult.projectGraph,
6539
6739
  warnings,
6540
6740
  hallucinationCount: projectResult.hallucinations.length,
6541
- provenance: summarizeProvenance(projectResult.provenance)
6741
+ provenance: projectResult.provenance
6542
6742
  });
6543
6743
  return {
6544
6744
  domainProfile,
@@ -6549,6 +6749,7 @@ async function runDomainPipeline(options) {
6549
6749
  };
6550
6750
  }
6551
6751
 
6752
+ exports.AmbiguousDomainSchema = AmbiguousDomainSchema;
6552
6753
  exports.ArchEdgeSchema = ArchEdgeSchema;
6553
6754
  exports.ArchNodeSchema = ArchNodeSchema;
6554
6755
  exports.ArchitectureDetailSchema = ArchitectureDetailSchema;
@@ -6596,6 +6797,7 @@ exports.LEARNER_CLAUSE = LEARNER_CLAUSE;
6596
6797
  exports.LanguageConceptExtensionSchema = LanguageConceptExtensionSchema;
6597
6798
  exports.LearningModeSchema = LearningModeSchema;
6598
6799
  exports.LearningPathStepSchema = LearningPathStepSchema;
6800
+ exports.LlmClientError = LlmClientError;
6599
6801
  exports.MathConceptExtensionSchema = MathConceptExtensionSchema;
6600
6802
  exports.MathTheoremSchema = MathTheoremSchema;
6601
6803
  exports.MilestoneDecompositionSchema = MilestoneDecompositionSchema;
@@ -6634,9 +6836,11 @@ exports.auditDepthLayers = auditDepthLayers;
6634
6836
  exports.auditPhasePlan = auditPhasePlan;
6635
6837
  exports.auditSyllabusCoverage = auditSyllabusCoverage;
6636
6838
  exports.breakPrerequisiteCycles = breakPrerequisiteCycles;
6839
+ exports.build9routerProvider = build9routerProvider;
6637
6840
  exports.cioActionSlug = cioActionSlug;
6638
6841
  exports.cioCode = cioCode;
6639
6842
  exports.conceptCodeFromName = conceptCodeFromName;
6843
+ exports.createLlmClient = createLlmClient;
6640
6844
  exports.detectDomain = detectDomain;
6641
6845
  exports.detectDomainProfile = detectDomainProfile;
6642
6846
  exports.detectInputType = detectInputType;
@@ -6656,11 +6860,14 @@ exports.mergePythonResults = mergePythonResults;
6656
6860
  exports.mergeSwiftResults = mergeSwiftResults;
6657
6861
  exports.mergeTsResults = mergeTsResults;
6658
6862
  exports.parseCppFile = parseCppFile;
6863
+ exports.parseJsonFromLlm = parseJsonFromLlm;
6659
6864
  exports.parsePythonFile = parsePythonFile;
6660
6865
  exports.parseSwiftFile = parseSwiftFile;
6661
6866
  exports.parseSyllabus = parseSyllabus;
6662
6867
  exports.parseTsFile = parseTsFile;
6868
+ exports.resolve9routerChain = resolve9routerChain;
6663
6869
  exports.resolveConcepts = resolveConcepts;
6870
+ exports.responseJsonCompat = responseJsonCompat;
6664
6871
  exports.runDomainPipeline = runDomainPipeline;
6665
6872
  exports.runHybridGraphPipeline = runHybridGraphPipeline;
6666
6873
  exports.runKnowledgeGraphPipeline = runKnowledgeGraphPipeline;
@@ -6671,7 +6878,9 @@ exports.scoreFiles = scoreFiles;
6671
6878
  exports.selectRelevantFiles = selectRelevantFiles;
6672
6879
  exports.sioCode = sioCode;
6673
6880
  exports.standardStatement = standardStatement;
6881
+ exports.summarizeProvenance = summarizeProvenance;
6674
6882
  exports.techTagFor = techTagFor;
6883
+ exports.transportResultFrom = transportResultFrom;
6675
6884
  exports.uloCode = uloCode;
6676
6885
  exports.validateCurriculumFeed = validateCurriculumFeed;
6677
6886
  exports.verifyKnowledgeGraph = verifyKnowledgeGraph;