@thanh01.pmt/domain-kit 0.6.0 → 0.6.2
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/LICENSE +21 -0
- package/README.md +10 -10
- package/dist/assembly/index.d.cts +3 -2
- package/dist/assembly/index.d.ts +3 -2
- package/dist/chunk-7A2SG466.mjs +325 -0
- package/dist/chunk-7A2SG466.mjs.map +1 -0
- package/dist/{chunk-DRVOV5ZD.mjs → chunk-DSEJLTUO.mjs} +2 -2
- package/dist/chunk-DSEJLTUO.mjs.map +1 -0
- package/dist/{chunk-2OGNQUXC.mjs → chunk-GVBDM746.mjs} +152 -303
- package/dist/chunk-GVBDM746.mjs.map +1 -0
- package/dist/{chunk-ERDTHXQA.mjs → chunk-H4W3YJLK.mjs} +10 -3
- package/dist/chunk-H4W3YJLK.mjs.map +1 -0
- package/dist/chunk-HRSBIVAW.mjs +3 -0
- package/dist/{chunk-ZKLT27T4.mjs.map → chunk-HRSBIVAW.mjs.map} +1 -1
- package/dist/chunk-IBVXXVGE.mjs +14 -0
- package/dist/chunk-IBVXXVGE.mjs.map +1 -0
- package/dist/{chunk-67GXK3ZE.mjs → chunk-WD3P5V3B.mjs} +3 -2
- package/dist/chunk-WD3P5V3B.mjs.map +1 -0
- package/dist/{chunk-5OMSYKQP.mjs → chunk-ZHMIJEMA.mjs} +33 -18
- package/dist/chunk-ZHMIJEMA.mjs.map +1 -0
- package/dist/{conceptEscalator-DPrs4PwC.d.cts → conceptEscalator-BmMJD8C5.d.cts} +1 -1
- package/dist/{conceptEscalator-BCsw0ys_.d.ts → conceptEscalator-DMsehscs.d.ts} +1 -1
- package/dist/concepts/index.d.cts +4 -3
- package/dist/concepts/index.d.ts +4 -3
- package/dist/{cpp-CZUPqJt9.d.cts → cpp-vlDl6pCL.d.cts} +4 -4
- package/dist/{cpp-CZUPqJt9.d.ts → cpp-vlDl6pCL.d.ts} +4 -4
- package/dist/detector/index.cjs +1 -0
- package/dist/detector/index.cjs.map +1 -1
- package/dist/detector/index.d.cts +2 -2
- package/dist/detector/index.d.ts +2 -2
- package/dist/detector/index.mjs +1 -1
- package/dist/{domainProfileDetector-DHb1T0zB.d.ts → domainProfileDetector-6pjgL-6_.d.ts} +1 -1
- package/dist/{domainProfileDetector-bzugdQmS.d.cts → domainProfileDetector-BsADsuRV.d.cts} +1 -1
- package/dist/{domainProfileSchema-CwT3Ffsw.d.cts → domainProfileSchema-779FGk14.d.cts} +38 -1
- package/dist/{domainProfileSchema-CwT3Ffsw.d.ts → domainProfileSchema-779FGk14.d.ts} +38 -1
- package/dist/extractors/index.d.cts +3 -3
- package/dist/extractors/index.d.ts +3 -3
- package/dist/feed/index.d.cts +1 -1
- package/dist/feed/index.d.ts +1 -1
- package/dist/graph/index.cjs +236 -57
- package/dist/graph/index.cjs.map +1 -1
- package/dist/graph/index.d.cts +27 -7
- package/dist/graph/index.d.ts +27 -7
- package/dist/graph/index.mjs +4 -2
- package/dist/{hybridGraphPipeline-CocfYe4d.d.cts → hybridGraphPipeline-B4mamLim.d.cts} +1 -1
- package/dist/{hybridGraphPipeline-9dRKJj7r.d.ts → hybridGraphPipeline-D2_1GUva.d.ts} +1 -1
- package/dist/index.cjs +278 -68
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -9
- package/dist/index.d.ts +10 -9
- package/dist/index.mjs +12 -10
- package/dist/{keywordExtractor-BxlGRMHC.d.cts → keywordExtractor-1YAFntsn.d.cts} +1 -1
- package/dist/{keywordExtractor-DU5XRN8-.d.ts → keywordExtractor-DWw8z_AF.d.ts} +1 -1
- package/dist/parsers/index.cjs.map +1 -1
- package/dist/parsers/index.d.cts +1 -1
- package/dist/parsers/index.d.ts +1 -1
- package/dist/parsers/index.mjs +1 -1
- package/dist/pipeline/index.cjs +261 -68
- package/dist/pipeline/index.cjs.map +1 -1
- package/dist/pipeline/index.d.cts +10 -9
- package/dist/pipeline/index.d.ts +10 -9
- package/dist/pipeline/index.mjs +8 -6
- package/dist/schemas/index.cjs +8 -0
- package/dist/schemas/index.cjs.map +1 -1
- package/dist/schemas/index.d.cts +2 -2
- package/dist/schemas/index.d.ts +2 -2
- package/dist/schemas/index.mjs +1 -1
- package/dist/utils/llmClient.cjs +344 -0
- package/dist/utils/llmClient.cjs.map +1 -0
- package/dist/utils/llmClient.d.cts +118 -0
- package/dist/utils/llmClient.d.ts +118 -0
- package/dist/utils/llmClient.mjs +4 -0
- package/dist/utils/llmClient.mjs.map +1 -0
- package/dist/utils/llmTransportResult.cjs +16 -0
- package/dist/utils/llmTransportResult.cjs.map +1 -0
- package/dist/utils/llmTransportResult.d.cts +36 -0
- package/dist/utils/llmTransportResult.d.ts +36 -0
- package/dist/utils/llmTransportResult.mjs +3 -0
- package/dist/utils/llmTransportResult.mjs.map +1 -0
- package/package.json +9 -10
- package/dist/chunk-2OGNQUXC.mjs.map +0 -1
- package/dist/chunk-5OMSYKQP.mjs.map +0 -1
- package/dist/chunk-67GXK3ZE.mjs.map +0 -1
- package/dist/chunk-DRVOV5ZD.mjs.map +0 -1
- package/dist/chunk-ERDTHXQA.mjs.map +0 -1
- package/dist/chunk-ZKLT27T4.mjs +0 -3
- package/dist/llmClient-CX5uUiQ1.d.cts +0 -60
- package/dist/llmClient-CX5uUiQ1.d.ts +0 -60
- package/dist/{curriculumFeedSchema-DYayelAA.d.ts → curriculumFeedSchema-DK-TUAJZ.d.cts} +8 -8
- package/dist/{curriculumFeedSchema-DYayelAA.d.cts → curriculumFeedSchema-DK-TUAJZ.d.ts} +8 -8
package/dist/pipeline/index.cjs
CHANGED
|
@@ -526,6 +526,17 @@ function extractKeywords(sourceContext, basicAnalysis, repoDir, options) {
|
|
|
526
526
|
return uniqueKeywords;
|
|
527
527
|
}
|
|
528
528
|
|
|
529
|
+
// src/utils/llmTransportResult.ts
|
|
530
|
+
function transportResultFrom(provider, model, options) {
|
|
531
|
+
return {
|
|
532
|
+
provider,
|
|
533
|
+
model,
|
|
534
|
+
framingStripped: !!options?.framingStripped,
|
|
535
|
+
upstreamError: options?.upstreamError,
|
|
536
|
+
usage: options?.usage
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
|
|
529
540
|
// src/utils/llmClient.ts
|
|
530
541
|
function summarizeProvenance(callLog) {
|
|
531
542
|
const providers = [];
|
|
@@ -538,6 +549,44 @@ function summarizeProvenance(callLog) {
|
|
|
538
549
|
}
|
|
539
550
|
return { providers, models, calls: callLog.length, failed_calls: failed, degraded: failed > 0 };
|
|
540
551
|
}
|
|
552
|
+
async function responseJsonCompat(response) {
|
|
553
|
+
const text = await response.text();
|
|
554
|
+
try {
|
|
555
|
+
return JSON.parse(text);
|
|
556
|
+
} catch {
|
|
557
|
+
const start = text.indexOf("{");
|
|
558
|
+
if (start < 0) {
|
|
559
|
+
throw new LlmClientError(`LLM API returned a non-JSON body: ${text.slice(0, 200)}`);
|
|
560
|
+
}
|
|
561
|
+
let depth = 0;
|
|
562
|
+
let inString = false;
|
|
563
|
+
let escape = false;
|
|
564
|
+
for (let i = start; i < text.length; i++) {
|
|
565
|
+
const ch = text[i];
|
|
566
|
+
if (escape) {
|
|
567
|
+
escape = false;
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
if (ch === "\\") {
|
|
571
|
+
escape = true;
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
574
|
+
if (ch === '"') {
|
|
575
|
+
inString = !inString;
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
if (inString) continue;
|
|
579
|
+
if (ch === "{") depth++;
|
|
580
|
+
if (ch === "}") {
|
|
581
|
+
depth--;
|
|
582
|
+
if (depth === 0) {
|
|
583
|
+
return JSON.parse(text.slice(start, i + 1));
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
throw new LlmClientError(`LLM API returned an unterminated JSON body: ${text.slice(0, 200)}`);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
541
590
|
var LlmClientError = class extends Error {
|
|
542
591
|
constructor(message, status, cause) {
|
|
543
592
|
super(message);
|
|
@@ -546,28 +595,22 @@ var LlmClientError = class extends Error {
|
|
|
546
595
|
this.name = "LlmClientError";
|
|
547
596
|
}
|
|
548
597
|
};
|
|
549
|
-
function
|
|
550
|
-
const
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
};
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
if (
|
|
564
|
-
|
|
565
|
-
if (or) {
|
|
566
|
-
const rest = chain.filter((c) => c !== or);
|
|
567
|
-
return [or, ...rest];
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
return chain;
|
|
598
|
+
function build9routerProvider(model) {
|
|
599
|
+
const apiKey = process.env.NINEROUTER_API_KEY;
|
|
600
|
+
if (!apiKey) return null;
|
|
601
|
+
const baseUrl = (process.env.NINEROUTER_BASE_URL || "https://ai-router.orchable.app/v1").replace(/^["']|["']$/g, "").trim();
|
|
602
|
+
const label = "9router";
|
|
603
|
+
const modelName = (model || process.env.NINEROUTER_MODEL || "laguna-s-2.1").trim();
|
|
604
|
+
return { label, baseUrl, apiKey, model: modelName };
|
|
605
|
+
}
|
|
606
|
+
function resolve9routerChain(configModel) {
|
|
607
|
+
const primary = build9routerProvider(configModel);
|
|
608
|
+
if (!primary) return [];
|
|
609
|
+
const alt = process.env.NINEROUTER_MODEL_ALT && process.env.NINEROUTER_MODEL_ALT.trim();
|
|
610
|
+
if (!alt) return [primary];
|
|
611
|
+
const secondary = build9routerProvider(alt);
|
|
612
|
+
if (!secondary) return [primary];
|
|
613
|
+
return primary.model === secondary.model ? [primary] : [primary, secondary];
|
|
571
614
|
}
|
|
572
615
|
var MIN_REQUEST_INTERVAL_MS = 2500;
|
|
573
616
|
var requestIntervalMs = Number(process.env.LLM_MIN_REQUEST_INTERVAL_MS || MIN_REQUEST_INTERVAL_MS);
|
|
@@ -578,11 +621,11 @@ async function pacedDelay() {
|
|
|
578
621
|
lastRequestAt = Date.now();
|
|
579
622
|
}
|
|
580
623
|
function createLlmClient(config) {
|
|
581
|
-
const chain =
|
|
624
|
+
const chain = resolve9routerChain(config?.model);
|
|
582
625
|
const primary = chain[0];
|
|
583
626
|
if (!primary) {
|
|
584
627
|
throw new LlmClientError(
|
|
585
|
-
"No
|
|
628
|
+
"No 9router provider configured. Set NINEROUTER_API_KEY and NINEROUTER_MODEL (or the NINEROUTER_* env defaults expected by the kit)."
|
|
586
629
|
);
|
|
587
630
|
}
|
|
588
631
|
primary.apiKey;
|
|
@@ -664,17 +707,22 @@ function createLlmClient(config) {
|
|
|
664
707
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
665
708
|
});
|
|
666
709
|
if (!retry.ok) throw err;
|
|
667
|
-
const retryData = await retry
|
|
710
|
+
const retryData = await responseJsonCompat(retry);
|
|
668
711
|
const retryContent2 = retryData.choices?.[0]?.message?.content || "";
|
|
669
712
|
if (!retryContent2.trim()) throw err;
|
|
670
713
|
callLog[recIdx] = { ...callLog[recIdx], ok: true };
|
|
714
|
+
const transport2 = transportResultFrom(provider.label, retryData.model || provider.model, {
|
|
715
|
+
usage: retryData.usage,
|
|
716
|
+
framingStripped: true
|
|
717
|
+
});
|
|
671
718
|
return {
|
|
672
719
|
content: retryContent2,
|
|
673
720
|
model: retryData.model || provider.model,
|
|
674
721
|
provider: provider.label,
|
|
675
722
|
attempts: attempts.slice(),
|
|
676
723
|
usage: retryData.usage,
|
|
677
|
-
finishReason: retryData.choices?.[0]?.finish_reason
|
|
724
|
+
finishReason: retryData.choices?.[0]?.finish_reason,
|
|
725
|
+
_transport: transport2
|
|
678
726
|
};
|
|
679
727
|
}
|
|
680
728
|
if (TRANSIENT.has(response.status)) {
|
|
@@ -694,21 +742,26 @@ function createLlmClient(config) {
|
|
|
694
742
|
})
|
|
695
743
|
});
|
|
696
744
|
if (!retry.ok) throw err;
|
|
697
|
-
const retryData = await retry
|
|
745
|
+
const retryData = await responseJsonCompat(retry);
|
|
698
746
|
const retryContent = retryData.choices?.[0]?.message?.content || "";
|
|
699
747
|
callLog[recIdx] = { ...callLog[recIdx], ok: true };
|
|
748
|
+
const transport2 = transportResultFrom(provider.label, retryData.model || provider.model, {
|
|
749
|
+
usage: retryData.usage,
|
|
750
|
+
framingStripped: true
|
|
751
|
+
});
|
|
700
752
|
return {
|
|
701
753
|
content: retryContent,
|
|
702
754
|
model: retryData.model || provider.model,
|
|
703
755
|
provider: provider.label,
|
|
704
756
|
attempts: attempts.slice(),
|
|
705
757
|
usage: retryData.usage,
|
|
706
|
-
finishReason: retryData.choices?.[0]?.finish_reason
|
|
758
|
+
finishReason: retryData.choices?.[0]?.finish_reason,
|
|
759
|
+
_transport: transport2
|
|
707
760
|
};
|
|
708
761
|
}
|
|
709
762
|
throw err;
|
|
710
763
|
}
|
|
711
|
-
const data = await response
|
|
764
|
+
const data = await responseJsonCompat(response);
|
|
712
765
|
const wrappedError = data.error;
|
|
713
766
|
if (wrappedError) {
|
|
714
767
|
throw new LlmClientError("[" + provider.label + "] upstream error " + (wrappedError.code ?? "") + ": " + (wrappedError.message ?? "unknown"));
|
|
@@ -717,13 +770,18 @@ function createLlmClient(config) {
|
|
|
717
770
|
if (!content.trim()) {
|
|
718
771
|
throw new LlmClientError("[" + provider.label + "] empty completion returned");
|
|
719
772
|
}
|
|
773
|
+
const transport = transportResultFrom(provider.label, data.model || provider.model, {
|
|
774
|
+
usage: data.usage,
|
|
775
|
+
framingStripped: true
|
|
776
|
+
});
|
|
720
777
|
return {
|
|
721
778
|
content,
|
|
722
779
|
model: data.model || provider.model,
|
|
723
780
|
provider: provider.label,
|
|
724
781
|
attempts: attempts.slice(),
|
|
725
782
|
usage: data.usage,
|
|
726
|
-
finishReason: data.choices?.[0]?.finish_reason
|
|
783
|
+
finishReason: data.choices?.[0]?.finish_reason,
|
|
784
|
+
_transport: transport
|
|
727
785
|
};
|
|
728
786
|
} catch (err) {
|
|
729
787
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -897,7 +955,7 @@ async function extractProjectOverview(options) {
|
|
|
897
955
|
(kw) => `- ${kw.keyword} (from ${kw.source})`
|
|
898
956
|
);
|
|
899
957
|
if (kwItems.length > 0) {
|
|
900
|
-
keywordsAnchor = "\
|
|
958
|
+
keywordsAnchor = "\nDomain Keywords & Patterns (from regex-based static scan \u2014 use as context anchor):\n" + kwItems.join("\n") + "\n";
|
|
901
959
|
}
|
|
902
960
|
}
|
|
903
961
|
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.";
|
|
@@ -1946,7 +2004,7 @@ async function runProjectGraphPipeline(options) {
|
|
|
1946
2004
|
const filePaths = selectedPaths;
|
|
1947
2005
|
const primaryLang = detectPrimaryLanguage(filePaths);
|
|
1948
2006
|
log("STEP_1", `Primary language: ${primaryLang}, ${filePaths.length} files loaded (${fileContentsMap.size} with content)`);
|
|
1949
|
-
log("STEP_1", "Parsing source files with
|
|
2007
|
+
log("STEP_1", "Parsing source files with regex-based scan (no AST library)...");
|
|
1950
2008
|
const allParseResults = [];
|
|
1951
2009
|
for (const [relPath, content] of fileContentsMap) {
|
|
1952
2010
|
const fullPath = relPath;
|
|
@@ -1970,7 +2028,7 @@ async function runProjectGraphPipeline(options) {
|
|
|
1970
2028
|
} else {
|
|
1971
2029
|
mergedContext = mergeCppResults(allParseResults.filter((r) => "error_handling" in r && !("docstrings" in r) && !("property_wrappers" in r)));
|
|
1972
2030
|
}
|
|
1973
|
-
log("STEP_2", "Extracting keywords from
|
|
2031
|
+
log("STEP_2", "Extracting keywords from regex-based source scan...");
|
|
1974
2032
|
const astKeywords = extractKeywords(mergedContext);
|
|
1975
2033
|
log("STEP_2", `Extracted ${astKeywords.length} keywords`);
|
|
1976
2034
|
log("STEP_3", "Building SDK API index...");
|
|
@@ -2710,7 +2768,110 @@ function techSignalHits(text) {
|
|
|
2710
2768
|
}
|
|
2711
2769
|
return hits;
|
|
2712
2770
|
}
|
|
2713
|
-
|
|
2771
|
+
var COMPLETABLE_VERBS = [
|
|
2772
|
+
"build",
|
|
2773
|
+
"create",
|
|
2774
|
+
"implement",
|
|
2775
|
+
"write",
|
|
2776
|
+
"configure",
|
|
2777
|
+
"add",
|
|
2778
|
+
"use",
|
|
2779
|
+
"apply",
|
|
2780
|
+
"display",
|
|
2781
|
+
"store",
|
|
2782
|
+
"load",
|
|
2783
|
+
"save",
|
|
2784
|
+
"handle",
|
|
2785
|
+
"parse",
|
|
2786
|
+
"validate",
|
|
2787
|
+
"test",
|
|
2788
|
+
"register",
|
|
2789
|
+
"compute",
|
|
2790
|
+
"generate",
|
|
2791
|
+
"print",
|
|
2792
|
+
"format",
|
|
2793
|
+
"convert",
|
|
2794
|
+
"extend",
|
|
2795
|
+
"define",
|
|
2796
|
+
"execute",
|
|
2797
|
+
"run",
|
|
2798
|
+
"read",
|
|
2799
|
+
"fetch",
|
|
2800
|
+
"wrap",
|
|
2801
|
+
"log",
|
|
2802
|
+
"call",
|
|
2803
|
+
"raise",
|
|
2804
|
+
"install",
|
|
2805
|
+
"import",
|
|
2806
|
+
"export",
|
|
2807
|
+
"declare",
|
|
2808
|
+
"embed",
|
|
2809
|
+
"model",
|
|
2810
|
+
"represent",
|
|
2811
|
+
"override",
|
|
2812
|
+
"customize",
|
|
2813
|
+
"enforce",
|
|
2814
|
+
"mock",
|
|
2815
|
+
"assert",
|
|
2816
|
+
"query",
|
|
2817
|
+
"index",
|
|
2818
|
+
"cache",
|
|
2819
|
+
"stream",
|
|
2820
|
+
"split",
|
|
2821
|
+
"join",
|
|
2822
|
+
"strip",
|
|
2823
|
+
"encode",
|
|
2824
|
+
"decode",
|
|
2825
|
+
"traverse",
|
|
2826
|
+
"measure",
|
|
2827
|
+
"evaluate",
|
|
2828
|
+
"benchmark",
|
|
2829
|
+
"diagnose",
|
|
2830
|
+
"demonstrate",
|
|
2831
|
+
"verify",
|
|
2832
|
+
"organize",
|
|
2833
|
+
"structure",
|
|
2834
|
+
"choose",
|
|
2835
|
+
"select",
|
|
2836
|
+
"compare",
|
|
2837
|
+
"distinguish",
|
|
2838
|
+
"attach",
|
|
2839
|
+
"annotate",
|
|
2840
|
+
"expose",
|
|
2841
|
+
"accept",
|
|
2842
|
+
"manage",
|
|
2843
|
+
"group",
|
|
2844
|
+
"sort",
|
|
2845
|
+
"filter",
|
|
2846
|
+
"map",
|
|
2847
|
+
"reduce",
|
|
2848
|
+
"catch",
|
|
2849
|
+
"throw",
|
|
2850
|
+
"migrate",
|
|
2851
|
+
"inspect",
|
|
2852
|
+
"trace",
|
|
2853
|
+
"parameterize",
|
|
2854
|
+
"construct",
|
|
2855
|
+
"design",
|
|
2856
|
+
"capture",
|
|
2857
|
+
// Completable implementation verbs present in production SIO output but
|
|
2858
|
+
// missing from the original registry (2026-10-07 smoke test, swift kg):
|
|
2859
|
+
"update",
|
|
2860
|
+
"inject",
|
|
2861
|
+
"compose",
|
|
2862
|
+
"pass",
|
|
2863
|
+
"modify",
|
|
2864
|
+
"connect",
|
|
2865
|
+
"remove",
|
|
2866
|
+
"transform",
|
|
2867
|
+
"trigger",
|
|
2868
|
+
"refresh",
|
|
2869
|
+
"persist",
|
|
2870
|
+
"observe",
|
|
2871
|
+
"arrange",
|
|
2872
|
+
"derive"
|
|
2873
|
+
];
|
|
2874
|
+
function auditDepthLayers(concepts, transport) {
|
|
2714
2875
|
return concepts.map((c) => {
|
|
2715
2876
|
const ulo = (c.ulo ?? "").trim();
|
|
2716
2877
|
const cio = (c.cio ?? "").trim();
|
|
@@ -2731,7 +2892,7 @@ function auditDepthLayers(concepts) {
|
|
|
2731
2892
|
});
|
|
2732
2893
|
const conceptKeywords = (c.keywords ?? []).map((k) => k.toLowerCase()).filter(Boolean);
|
|
2733
2894
|
const sioOffConceptItems = conceptKeywords.length > 0 ? sioItems.filter((s) => !boundToConcept(s)) : [];
|
|
2734
|
-
const COMPLETABLE =
|
|
2895
|
+
const COMPLETABLE = new RegExp("\\b(?:" + COMPLETABLE_VERBS.join("|") + ")\\b", "i");
|
|
2735
2896
|
const sioNotCompletable = sioItems.filter((s) => !COMPLETABLE.test(s) && !/`[^`]+`/.test(s));
|
|
2736
2897
|
const density = (text) => {
|
|
2737
2898
|
if (!text) return 0;
|
|
@@ -2771,7 +2932,8 @@ function auditDepthLayers(concepts) {
|
|
|
2771
2932
|
clauseOk,
|
|
2772
2933
|
codesOk,
|
|
2773
2934
|
codeIssues,
|
|
2774
|
-
passed
|
|
2935
|
+
passed,
|
|
2936
|
+
transport
|
|
2775
2937
|
};
|
|
2776
2938
|
});
|
|
2777
2939
|
}
|
|
@@ -2781,14 +2943,14 @@ function excerpt(text, max) {
|
|
|
2781
2943
|
const t = (text || "").trim();
|
|
2782
2944
|
return t.length > max ? t.slice(0, max) + "..." : t;
|
|
2783
2945
|
}
|
|
2784
|
-
async function chatJson(client, system, user) {
|
|
2946
|
+
async function chatJson(client, system, user, transport) {
|
|
2785
2947
|
const r = await client.chat(
|
|
2786
2948
|
[{ role: "system", content: system }, { role: "user", content: user }],
|
|
2787
2949
|
{ temperature: 0.1, maxTokens: parseInt(process.env.KG_MAX_TOKENS || "65536", 10) }
|
|
2788
2950
|
);
|
|
2789
2951
|
return parseJsonFromLlm(r.content);
|
|
2790
2952
|
}
|
|
2791
|
-
var LO_BATCH_SIZE =
|
|
2953
|
+
var LO_BATCH_SIZE = 10;
|
|
2792
2954
|
function chunkConcepts(items, size) {
|
|
2793
2955
|
const out = [];
|
|
2794
2956
|
for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
|
|
@@ -2827,7 +2989,7 @@ async function authorDepthLayersHierarchically(client, concepts, techStack, warn
|
|
|
2827
2989
|
for (const batch of chunkConcepts(concepts, LO_BATCH_SIZE)) {
|
|
2828
2990
|
const batchList = batch.map((c) => "- " + c.id + " (" + c.name + ")" + (c.description ? ": " + excerpt(c.description, 160) : "")).join("\n");
|
|
2829
2991
|
const batchUser = "Concepts:\n" + batchList + '\n\nReturn JSON:\n{"ulos":[{"concept_id":"C1","ulo":"..."}]}';
|
|
2830
|
-
const uloRes = await chatJson(client, uloSystem, batchUser);
|
|
2992
|
+
const uloRes = await chatJson(client, uloSystem, batchUser, transportResultFrom("9router", client.model, { framingStripped: true }));
|
|
2831
2993
|
for (const u of uloRes.ulos ?? []) {
|
|
2832
2994
|
if (u.concept_id && u.ulo) uloByConcept[u.concept_id] = standardStatement(u.ulo);
|
|
2833
2995
|
}
|
|
@@ -2866,7 +3028,7 @@ async function authorDepthLayersHierarchically(client, concepts, techStack, warn
|
|
|
2866
3028
|
new Set(batch.map((c) => c.id));
|
|
2867
3029
|
const batchUloContext = batch.map((c) => "- " + c.id + " (" + c.name + "): " + (uloByConcept[c.id] || "(missing)")).join("\n");
|
|
2868
3030
|
const batchCioUser = "Approved ULOs:\n" + batchUloContext + '\n\nReturn JSON:\n{"cios":[{"concept_id":"C1","cio":"..."}]}';
|
|
2869
|
-
const cioRes = await chatJson(client, cioSystem, batchCioUser);
|
|
3031
|
+
const cioRes = await chatJson(client, cioSystem, batchCioUser, transportResultFrom("9router", client.model, { framingStripped: true }));
|
|
2870
3032
|
for (const x of cioRes.cios ?? []) {
|
|
2871
3033
|
if (x.concept_id && x.cio) cioByConcept[x.concept_id] = standardStatement(x.cio);
|
|
2872
3034
|
}
|
|
@@ -2891,15 +3053,7 @@ async function authorDepthLayersHierarchically(client, concepts, techStack, warn
|
|
|
2891
3053
|
" context where it is used. Application/Analysis/Creation skills are assessed",
|
|
2892
3054
|
" INDIRECTLY through completing these tasks \u2014 there is no fixed Bloom ceiling.",
|
|
2893
3055
|
" MACHINE CHECK: every item MUST contain at least one ACTION VERB from this whitelist",
|
|
2894
|
-
" (
|
|
2895
|
-
" save, handle, parse, validate, test, register, compute, generate, print, format,",
|
|
2896
|
-
" convert, extend, define, execute, run, read, fetch, wrap, log, call, raise, install,",
|
|
2897
|
-
" import, export, declare, embed, model, represent, override, customize, enforce, mock,",
|
|
2898
|
-
" assert, query, index, cache, stream, split, join, strip, encode, decode, traverse,",
|
|
2899
|
-
" measure, evaluate, benchmark, diagnose, demonstrate, verify, organize, structure,",
|
|
2900
|
-
" choose, select, compare, distinguish, attach, annotate, expose, accept, manage, group,",
|
|
2901
|
-
" sort, filter, map, reduce, catch, throw, migrate, inspect, trace, parameterize,",
|
|
2902
|
-
" construct, design, capture) \u2014 OR wrap the concrete construct in backticks.",
|
|
3056
|
+
" (" + COMPLETABLE_VERBS.join(", ") + ") \u2014 OR wrap the concrete construct in backticks.",
|
|
2903
3057
|
" Items with abstract verbs (understand, learn, know, grasp, appreciate, explore) and",
|
|
2904
3058
|
" no backtick construct FAIL the audit. Prefer starting the task phrase with the verb.",
|
|
2905
3059
|
"3. 2-3 items per concept, each referencing a DIFFERENT concrete construct.",
|
|
@@ -2927,7 +3081,7 @@ async function authorDepthLayersHierarchically(client, concepts, techStack, warn
|
|
|
2927
3081
|
"Return JSON:",
|
|
2928
3082
|
'{"sios":[{"concept_id":"C1","sio":["...","..."]}]}'
|
|
2929
3083
|
].join("\n");
|
|
2930
|
-
const sioRes = await chatJson(client, sioSystem, batchSioUser);
|
|
3084
|
+
const sioRes = await chatJson(client, sioSystem, batchSioUser, transportResultFrom("9router", client.model, { framingStripped: true }));
|
|
2931
3085
|
for (const x of sioRes.sios ?? []) {
|
|
2932
3086
|
if (x.concept_id && Array.isArray(x.sio)) sioByConcept[x.concept_id] = x.sio.map((item) => standardStatement(String(item)));
|
|
2933
3087
|
}
|
|
@@ -2980,7 +3134,7 @@ async function authorDepthLayersHierarchically(client, concepts, techStack, warn
|
|
|
2980
3134
|
"- CIO: HOW mechanism, technology-agnostic, more concrete than ULO, Marr 2-Language neutral.",
|
|
2981
3135
|
"- SIO: keyword-bound completable implementation tasks naming real constructs of " + techStack + ".",
|
|
2982
3136
|
"- 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).",
|
|
2983
|
-
"- COMPLETABILITY RULE: every SIO item must contain an actionable verb (build, create,
|
|
3137
|
+
"- 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.",
|
|
2984
3138
|
"",
|
|
2985
3139
|
JSON_ONLY
|
|
2986
3140
|
].join("\n");
|
|
@@ -3028,7 +3182,7 @@ async function authorDepthLayersHierarchically(client, concepts, techStack, warn
|
|
|
3028
3182
|
"Depth-layer contract audit failed after repair for: " + detail + " \u2014 regenerate or fix the prompt contract (fail-closed, no silent fallback)"
|
|
3029
3183
|
);
|
|
3030
3184
|
}
|
|
3031
|
-
return { uloByConcept, cioByConcept, sioByConcept, audits, warnings };
|
|
3185
|
+
return { uloByConcept, cioByConcept, sioByConcept, audits, warnings, lastTransport: audits[0]?.transport };
|
|
3032
3186
|
}
|
|
3033
3187
|
function cell(v) {
|
|
3034
3188
|
return (v || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -3171,7 +3325,7 @@ async function standardizeConcepts(opts) {
|
|
|
3171
3325
|
" Adopt the tree's terminology. Only propose new when the tree genuinely has NO concept teaching that knowledge (broader AND narrower both fail).",
|
|
3172
3326
|
' 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.',
|
|
3173
3327
|
" If between 1 and 3 tree concepts are CLOSE but not exact, list their codes in possible_tree_matches for human review.",
|
|
3174
|
-
`For a "standardized" decision ALSO return "adopted_keywords":
|
|
3328
|
+
`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.`,
|
|
3175
3329
|
"Rules:",
|
|
3176
3330
|
"- Judge by MEANING (definition + keywords), not by string similarity.",
|
|
3177
3331
|
'- 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.',
|
|
@@ -3211,10 +3365,25 @@ async function standardizeConcepts(opts) {
|
|
|
3211
3365
|
const tc = catalog.concepts.find((x) => x.code === d.tree_code);
|
|
3212
3366
|
if (!tc) throw new Error("Concept " + c.id + " standardized to unknown tree code " + d.tree_code + " \u2014 fail-closed");
|
|
3213
3367
|
const treeKw = new Set(tc.keywords.map((k) => k.toLowerCase()));
|
|
3214
|
-
const
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3368
|
+
const rawAdopted = (d.adopted_keywords || []).map((k) => String(k)).filter(Boolean);
|
|
3369
|
+
let adopted;
|
|
3370
|
+
if (tc.keywords.length === 0) {
|
|
3371
|
+
adopted = rawAdopted.filter((k) => (c.keywords ?? []).some((ck) => ck.toLowerCase() === k.toLowerCase()));
|
|
3372
|
+
if (adopted.length === 0) adopted = (c.keywords ?? []).slice(0, 8);
|
|
3373
|
+
warnings.push("Concept " + c.id + ": tree concept " + tc.code + " has no keyword registry \u2014 adopted " + adopted.length + " keyword(s) from the analysis concept");
|
|
3374
|
+
} else {
|
|
3375
|
+
const norm = (k) => k.toLowerCase().replace(/ies$/, "y").replace(/s$/, "");
|
|
3376
|
+
const treeKwNorm = new Set([...treeKw].map(norm));
|
|
3377
|
+
const dropped = [];
|
|
3378
|
+
adopted = rawAdopted.filter((k) => {
|
|
3379
|
+
if (treeKw.has(k.toLowerCase()) || treeKwNorm.has(norm(k))) return true;
|
|
3380
|
+
dropped.push(k);
|
|
3381
|
+
return false;
|
|
3382
|
+
});
|
|
3383
|
+
if (dropped.length > 0) {
|
|
3384
|
+
warnings.push("Concept " + c.id + ": dropped " + dropped.length + " adopted keyword(s) not in tree concept " + tc.code + " registry: " + dropped.join(", "));
|
|
3385
|
+
}
|
|
3386
|
+
}
|
|
3218
3387
|
if (tc.keywords.length > 0 && adopted.length === 0) {
|
|
3219
3388
|
throw new Error("Concept " + c.id + " standardized to " + tc.code + " without adopted_keywords \u2014 fail-closed");
|
|
3220
3389
|
}
|
|
@@ -3309,7 +3478,7 @@ async function generateKnowledgeGraph(options) {
|
|
|
3309
3478
|
log("STEP_1", `Parsed syllabus: ${parsedSyllabus.units.length} units, ${parsedSyllabus.allTopics.length} topics`);
|
|
3310
3479
|
}
|
|
3311
3480
|
const client = createLlmClient(llmConfig);
|
|
3312
|
-
const maxSyllabusChars = parseInt(process.env.KG_MAX_SYLLABUS_CHARS || "
|
|
3481
|
+
const maxSyllabusChars = parseInt(process.env.KG_MAX_SYLLABUS_CHARS || "32000", 10);
|
|
3313
3482
|
let truncatedSyllabus = "";
|
|
3314
3483
|
if (syllabusText) {
|
|
3315
3484
|
if (syllabusText.length > maxSyllabusChars) {
|
|
@@ -3499,23 +3668,32 @@ Return JSON:
|
|
|
3499
3668
|
}
|
|
3500
3669
|
}
|
|
3501
3670
|
const idByTreeCode = new Map(concepts.filter((c) => c.tree_code).map((c) => [c.tree_code, c.id]));
|
|
3671
|
+
const unmappedTreePrereqs = /* @__PURE__ */ new Set();
|
|
3502
3672
|
for (const c of concepts) {
|
|
3503
3673
|
for (const tp of c.tree_prerequisites || []) {
|
|
3504
3674
|
const target = idByTreeCode.get(tp);
|
|
3505
3675
|
if (target && target !== c.id && !(c.prerequisites || []).includes(target)) {
|
|
3506
3676
|
c.prerequisites = [...c.prerequisites || [], target];
|
|
3507
3677
|
warnings.push("Prereq seeded from tree: " + c.id + " <- " + tp + " (" + target + ")");
|
|
3678
|
+
} else if (!target) {
|
|
3679
|
+
unmappedTreePrereqs.add(tp);
|
|
3508
3680
|
}
|
|
3509
3681
|
}
|
|
3510
3682
|
}
|
|
3683
|
+
if (unmappedTreePrereqs.size > 0) {
|
|
3684
|
+
const shown = [...unmappedTreePrereqs].slice(0, 10).join(", ");
|
|
3685
|
+
warnings.push("Tree prereqs outside THIS analysis (no edge seeded, " + unmappedTreePrereqs.size + " codes): " + shown + (unmappedTreePrereqs.size > 10 ? ", ..." : ""));
|
|
3686
|
+
}
|
|
3511
3687
|
log("STEP_2_5", "Standardized: " + std.decisions.filter((d) => d.decision === "standardized").length + ", proposed new: " + std.decisions.filter((d) => d.decision === "proposed_new").length);
|
|
3688
|
+
} else {
|
|
3689
|
+
warnings.push("No knowledgeTreePath provided \u2014 concepts are NOT standardized against the knowledge tree (all proposed_new)");
|
|
3512
3690
|
}
|
|
3513
3691
|
log("STEP_3", "Checking for prerequisite cycles...");
|
|
3514
3692
|
const cycles = detectPrerequisiteCycles(concepts);
|
|
3515
3693
|
if (cycles.length > 0) {
|
|
3516
3694
|
log("STEP_3", `Found ${cycles.length} cycle(s) in prerequisite chain \u2014 breaking cycles`);
|
|
3517
3695
|
for (const cycle of cycles) {
|
|
3518
|
-
warnings.push(`Cycle detected: ${cycle.join(" \u2192 ")} \u2014
|
|
3696
|
+
warnings.push(`Cycle detected: ${cycle.join(" \u2192 ")} \u2014 removing the closing back-edge (last DFS edge; pedagogically arbitrary)`);
|
|
3519
3697
|
}
|
|
3520
3698
|
const { fixed, removed } = breakPrerequisiteCycles(concepts, cycles);
|
|
3521
3699
|
for (let i = 0; i < concepts.length; i++) {
|
|
@@ -4432,6 +4610,15 @@ function emitCurriculumFeed(graph, opts = {}) {
|
|
|
4432
4610
|
return emitFromProject(g, opts);
|
|
4433
4611
|
}
|
|
4434
4612
|
|
|
4613
|
+
// src/pipeline/emitFeed.ts
|
|
4614
|
+
function emitNormalizedFeed(req) {
|
|
4615
|
+
return emitCurriculumFeed(req.graph, {
|
|
4616
|
+
warnings: req.warnings,
|
|
4617
|
+
hallucinationCount: req.hallucinationCount,
|
|
4618
|
+
provenance: req.provenance ? summarizeProvenance(req.provenance) : void 0
|
|
4619
|
+
});
|
|
4620
|
+
}
|
|
4621
|
+
|
|
4435
4622
|
// src/pipeline/knowledgeGraphPipeline.ts
|
|
4436
4623
|
async function runKnowledgeGraphPipeline(options) {
|
|
4437
4624
|
const warnings = [];
|
|
@@ -4452,9 +4639,10 @@ async function runKnowledgeGraphPipeline(options) {
|
|
|
4452
4639
|
parsedSyllabus
|
|
4453
4640
|
});
|
|
4454
4641
|
warnings.push(...result.warnings);
|
|
4455
|
-
const curriculumFeed =
|
|
4642
|
+
const curriculumFeed = emitNormalizedFeed({
|
|
4643
|
+
graph: result.knowledgeGraph,
|
|
4456
4644
|
warnings,
|
|
4457
|
-
provenance:
|
|
4645
|
+
provenance: result.provenance
|
|
4458
4646
|
});
|
|
4459
4647
|
return {
|
|
4460
4648
|
knowledgeGraph: result.knowledgeGraph,
|
|
@@ -4770,9 +4958,10 @@ async function runHybridGraphPipeline(options) {
|
|
|
4770
4958
|
const warnings = [];
|
|
4771
4959
|
const result = await generateHybridGraph(options);
|
|
4772
4960
|
warnings.push(...result.warnings);
|
|
4773
|
-
const curriculumFeed =
|
|
4961
|
+
const curriculumFeed = emitNormalizedFeed({
|
|
4962
|
+
graph: result.hybridGraph,
|
|
4774
4963
|
warnings,
|
|
4775
|
-
provenance:
|
|
4964
|
+
provenance: result.provenance
|
|
4776
4965
|
});
|
|
4777
4966
|
return {
|
|
4778
4967
|
hybridGraph: result.hybridGraph,
|
|
@@ -5651,6 +5840,7 @@ function detectDomainProfile(options) {
|
|
|
5651
5840
|
learningMode: modeResult.confidence
|
|
5652
5841
|
},
|
|
5653
5842
|
evidence: [...inputResult.evidence, ...domainResult.evidence, ...modeResult.evidence],
|
|
5843
|
+
ambiguous: domainResult.ambiguous,
|
|
5654
5844
|
detectedTechStack: domainResult.detectedTechStack,
|
|
5655
5845
|
detectedSubject: options.parsedSyllabus?.subject.title || domainResult.detectedSubject,
|
|
5656
5846
|
detectedDomain: domainResult.domainCategory,
|
|
@@ -5718,7 +5908,7 @@ async function runDomainPipeline(options) {
|
|
|
5718
5908
|
const projectResult2 = await runProjectGraphPipeline({
|
|
5719
5909
|
repoDir: options.repoDir,
|
|
5720
5910
|
goal: options.goal || "Build complete project",
|
|
5721
|
-
techStack: options.techStack || "typescript",
|
|
5911
|
+
techStack: options.techStack || domainProfile.detectedTechStack?.[0] || "typescript",
|
|
5722
5912
|
llmConfig: options.llmConfig,
|
|
5723
5913
|
embeddings: options.embeddings,
|
|
5724
5914
|
onProgress: options.onProgress
|
|
@@ -5728,8 +5918,9 @@ async function runDomainPipeline(options) {
|
|
|
5728
5918
|
description: options.description,
|
|
5729
5919
|
syllabusText: options.syllabusText,
|
|
5730
5920
|
parsedSyllabus: options.parsedSyllabus,
|
|
5921
|
+
gradeBand: options.gradeBand,
|
|
5731
5922
|
domain: domainProfile.domainCategory,
|
|
5732
|
-
techStack: options.techStack,
|
|
5923
|
+
techStack: options.techStack || domainProfile.detectedTechStack?.[0],
|
|
5733
5924
|
llmConfig: options.llmConfig,
|
|
5734
5925
|
onProgress: options.onProgress,
|
|
5735
5926
|
knowledgeTreePath: options.knowledgeTreePath,
|
|
@@ -5759,8 +5950,9 @@ async function runDomainPipeline(options) {
|
|
|
5759
5950
|
description: options.description,
|
|
5760
5951
|
syllabusText: options.syllabusText,
|
|
5761
5952
|
parsedSyllabus: options.parsedSyllabus,
|
|
5953
|
+
gradeBand: options.gradeBand,
|
|
5762
5954
|
domain: domainProfile.domainCategory,
|
|
5763
|
-
techStack: options.techStack,
|
|
5955
|
+
techStack: options.techStack || domainProfile.detectedTechStack?.[0],
|
|
5764
5956
|
llmConfig: options.llmConfig,
|
|
5765
5957
|
onProgress: options.onProgress,
|
|
5766
5958
|
knowledgeTreePath: options.knowledgeTreePath,
|
|
@@ -5789,10 +5981,11 @@ async function runDomainPipeline(options) {
|
|
|
5789
5981
|
embeddings: options.embeddings,
|
|
5790
5982
|
onProgress: options.onProgress
|
|
5791
5983
|
});
|
|
5792
|
-
const curriculumFeed =
|
|
5984
|
+
const curriculumFeed = emitNormalizedFeed({
|
|
5985
|
+
graph: projectResult.projectGraph,
|
|
5793
5986
|
warnings,
|
|
5794
5987
|
hallucinationCount: projectResult.hallucinations.length,
|
|
5795
|
-
provenance:
|
|
5988
|
+
provenance: projectResult.provenance
|
|
5796
5989
|
});
|
|
5797
5990
|
return {
|
|
5798
5991
|
domainProfile,
|