@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.
Files changed (90) 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-7A2SG466.mjs +325 -0
  6. package/dist/chunk-7A2SG466.mjs.map +1 -0
  7. package/dist/{chunk-DRVOV5ZD.mjs → chunk-DSEJLTUO.mjs} +2 -2
  8. package/dist/chunk-DSEJLTUO.mjs.map +1 -0
  9. package/dist/{chunk-2OGNQUXC.mjs → chunk-GVBDM746.mjs} +152 -303
  10. package/dist/chunk-GVBDM746.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-HRSBIVAW.mjs +3 -0
  14. package/dist/{chunk-ZKLT27T4.mjs.map → chunk-HRSBIVAW.mjs.map} +1 -1
  15. package/dist/chunk-IBVXXVGE.mjs +14 -0
  16. package/dist/chunk-IBVXXVGE.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/{chunk-5OMSYKQP.mjs → chunk-ZHMIJEMA.mjs} +33 -18
  20. package/dist/chunk-ZHMIJEMA.mjs.map +1 -0
  21. package/dist/{conceptEscalator-DPrs4PwC.d.cts → conceptEscalator-BmMJD8C5.d.cts} +1 -1
  22. package/dist/{conceptEscalator-BCsw0ys_.d.ts → conceptEscalator-DMsehscs.d.ts} +1 -1
  23. package/dist/concepts/index.d.cts +4 -3
  24. package/dist/concepts/index.d.ts +4 -3
  25. package/dist/{cpp-CZUPqJt9.d.cts → cpp-vlDl6pCL.d.cts} +4 -4
  26. package/dist/{cpp-CZUPqJt9.d.ts → cpp-vlDl6pCL.d.ts} +4 -4
  27. package/dist/detector/index.cjs +1 -0
  28. package/dist/detector/index.cjs.map +1 -1
  29. package/dist/detector/index.d.cts +2 -2
  30. package/dist/detector/index.d.ts +2 -2
  31. package/dist/detector/index.mjs +1 -1
  32. package/dist/{domainProfileDetector-DHb1T0zB.d.ts → domainProfileDetector-6pjgL-6_.d.ts} +1 -1
  33. package/dist/{domainProfileDetector-bzugdQmS.d.cts → domainProfileDetector-BsADsuRV.d.cts} +1 -1
  34. package/dist/{domainProfileSchema-CwT3Ffsw.d.cts → domainProfileSchema-779FGk14.d.cts} +38 -1
  35. package/dist/{domainProfileSchema-CwT3Ffsw.d.ts → domainProfileSchema-779FGk14.d.ts} +38 -1
  36. package/dist/extractors/index.d.cts +3 -3
  37. package/dist/extractors/index.d.ts +3 -3
  38. package/dist/feed/index.d.cts +1 -1
  39. package/dist/feed/index.d.ts +1 -1
  40. package/dist/graph/index.cjs +236 -57
  41. package/dist/graph/index.cjs.map +1 -1
  42. package/dist/graph/index.d.cts +27 -7
  43. package/dist/graph/index.d.ts +27 -7
  44. package/dist/graph/index.mjs +4 -2
  45. package/dist/{hybridGraphPipeline-CocfYe4d.d.cts → hybridGraphPipeline-B4mamLim.d.cts} +1 -1
  46. package/dist/{hybridGraphPipeline-9dRKJj7r.d.ts → hybridGraphPipeline-D2_1GUva.d.ts} +1 -1
  47. package/dist/index.cjs +278 -68
  48. package/dist/index.cjs.map +1 -1
  49. package/dist/index.d.cts +10 -9
  50. package/dist/index.d.ts +10 -9
  51. package/dist/index.mjs +12 -10
  52. package/dist/{keywordExtractor-BxlGRMHC.d.cts → keywordExtractor-1YAFntsn.d.cts} +1 -1
  53. package/dist/{keywordExtractor-DU5XRN8-.d.ts → keywordExtractor-DWw8z_AF.d.ts} +1 -1
  54. package/dist/parsers/index.cjs.map +1 -1
  55. package/dist/parsers/index.d.cts +1 -1
  56. package/dist/parsers/index.d.ts +1 -1
  57. package/dist/parsers/index.mjs +1 -1
  58. package/dist/pipeline/index.cjs +261 -68
  59. package/dist/pipeline/index.cjs.map +1 -1
  60. package/dist/pipeline/index.d.cts +10 -9
  61. package/dist/pipeline/index.d.ts +10 -9
  62. package/dist/pipeline/index.mjs +8 -6
  63. package/dist/schemas/index.cjs +8 -0
  64. package/dist/schemas/index.cjs.map +1 -1
  65. package/dist/schemas/index.d.cts +2 -2
  66. package/dist/schemas/index.d.ts +2 -2
  67. package/dist/schemas/index.mjs +1 -1
  68. package/dist/utils/llmClient.cjs +344 -0
  69. package/dist/utils/llmClient.cjs.map +1 -0
  70. package/dist/utils/llmClient.d.cts +118 -0
  71. package/dist/utils/llmClient.d.ts +118 -0
  72. package/dist/utils/llmClient.mjs +4 -0
  73. package/dist/utils/llmClient.mjs.map +1 -0
  74. package/dist/utils/llmTransportResult.cjs +16 -0
  75. package/dist/utils/llmTransportResult.cjs.map +1 -0
  76. package/dist/utils/llmTransportResult.d.cts +36 -0
  77. package/dist/utils/llmTransportResult.d.ts +36 -0
  78. package/dist/utils/llmTransportResult.mjs +3 -0
  79. package/dist/utils/llmTransportResult.mjs.map +1 -0
  80. package/package.json +9 -10
  81. package/dist/chunk-2OGNQUXC.mjs.map +0 -1
  82. package/dist/chunk-5OMSYKQP.mjs.map +0 -1
  83. package/dist/chunk-67GXK3ZE.mjs.map +0 -1
  84. package/dist/chunk-DRVOV5ZD.mjs.map +0 -1
  85. package/dist/chunk-ERDTHXQA.mjs.map +0 -1
  86. package/dist/chunk-ZKLT27T4.mjs +0 -3
  87. package/dist/llmClient-CX5uUiQ1.d.cts +0 -60
  88. package/dist/llmClient-CX5uUiQ1.d.ts +0 -60
  89. package/dist/{curriculumFeedSchema-DYayelAA.d.ts → curriculumFeedSchema-DK-TUAJZ.d.cts} +8 -8
  90. package/dist/{curriculumFeedSchema-DYayelAA.d.cts → curriculumFeedSchema-DK-TUAJZ.d.ts} +8 -8
@@ -1,282 +1,11 @@
1
+ import { createLlmClient, llmChatJson, parseJsonFromLlm } from './chunk-7A2SG466.mjs';
2
+ import { transportResultFrom } from './chunk-IBVXXVGE.mjs';
1
3
  import { parseSyllabus } from './chunk-T4Y2DE2W.mjs';
2
4
  import { stripDiacritics } from './chunk-OYSZQVPY.mjs';
3
5
  import { readFileSync, mkdirSync, writeFileSync } from 'fs';
4
6
  import { tmpdir } from 'os';
5
7
  import { join } from 'path';
6
8
 
7
- // src/utils/llmClient.ts
8
- function summarizeProvenance(callLog) {
9
- const providers = [];
10
- const models = [];
11
- let failed = 0;
12
- for (const r of callLog) {
13
- if (!providers.includes(r.provider)) providers.push(r.provider);
14
- if (!models.includes(r.model)) models.push(r.model);
15
- if (!r.ok) failed++;
16
- }
17
- return { providers, models, calls: callLog.length, failed_calls: failed, degraded: failed > 0 };
18
- }
19
- var LlmClientError = class extends Error {
20
- constructor(message, status, cause) {
21
- super(message);
22
- this.status = status;
23
- this.cause = cause;
24
- this.name = "LlmClientError";
25
- }
26
- };
27
- function resolveProviderChain(config) {
28
- const chain = [];
29
- const push = (label, baseUrl, apiKey, model) => {
30
- if (apiKey && baseUrl && model && !chain.some((c) => c.baseUrl === baseUrl && c.model === model)) {
31
- chain.push({ label, baseUrl, apiKey, model });
32
- }
33
- };
34
- if (config?.apiKey) {
35
- push("config", config.baseUrl || "https://api.openai.com/v1", config.apiKey, config.model || "gpt-4o-mini");
36
- }
37
- 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 || "");
38
- 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 || "");
39
- push("nvidia", "https://integrate.api.nvidia.com/v1", process.env.NVIDIA_API_KEY || "", process.env.NVIDIA_MODEL || "nvidia/nemotron-3-ultra-550b-a55b:free");
40
- push("openrouter", "https://openrouter.ai/api/v1", process.env.OPENROUTER_API_KEY || "", process.env.OPENROUTER_MODEL || "nvidia/nemotron-3-ultra-550b-a55b:free");
41
- if (process.env.OPENROUTER_MODEL) {
42
- const or = chain.find((c) => c.label === "openrouter");
43
- if (or) {
44
- const rest = chain.filter((c) => c !== or);
45
- return [or, ...rest];
46
- }
47
- }
48
- return chain;
49
- }
50
- var MIN_REQUEST_INTERVAL_MS = 2500;
51
- var requestIntervalMs = Number(process.env.LLM_MIN_REQUEST_INTERVAL_MS || MIN_REQUEST_INTERVAL_MS);
52
- var lastRequestAt = 0;
53
- async function pacedDelay() {
54
- const wait = lastRequestAt + requestIntervalMs - Date.now();
55
- if (wait > 0) await new Promise((resolve) => setTimeout(resolve, wait));
56
- lastRequestAt = Date.now();
57
- }
58
- function createLlmClient(config) {
59
- const chain = resolveProviderChain(config);
60
- const primary = chain[0];
61
- if (!primary) {
62
- throw new LlmClientError(
63
- "No LLM provider configured. Set NVIDIA_API_KEY (primary) and/or OPENROUTER_API_KEY (fallback), or pass llmConfig."
64
- );
65
- }
66
- primary.apiKey;
67
- primary.baseUrl;
68
- const model = primary.model;
69
- if (config?.minRequestIntervalMs !== void 0) {
70
- requestIntervalMs = Math.max(0, config.minRequestIntervalMs);
71
- }
72
- const callLog = [];
73
- async function chat(messages, options) {
74
- const temperature = options?.temperature ?? config?.temperature ?? 0.1;
75
- const maxTokens = options?.maxTokens ?? config?.maxTokens ?? parseInt(process.env.LLM_MAX_TOKENS || "65536", 10);
76
- const attempts = [];
77
- let lastError = null;
78
- const TRANSIENT = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
79
- const REQUEST_TIMEOUT_MS = Number(process.env.LLM_REQUEST_TIMEOUT_MS || 3e5);
80
- const MAX_CHAIN_ROUNDS = 3;
81
- for (let round = 1; round <= MAX_CHAIN_ROUNDS; round++) {
82
- if (round > 1) {
83
- attempts.push("round " + (round - 1) + " failed \u2014 backing off 20s before rewalking the chain");
84
- await new Promise((resolve) => setTimeout(resolve, 2e4));
85
- }
86
- for (const provider of chain) {
87
- const recIdx = callLog.push({ provider: provider.label, model: provider.model, ok: false }) - 1;
88
- try {
89
- const headers = {
90
- "Content-Type": "application/json",
91
- "Authorization": `Bearer ${provider.apiKey}`
92
- };
93
- const payload = {
94
- model: provider.model,
95
- messages,
96
- temperature,
97
- max_tokens: maxTokens
98
- };
99
- await pacedDelay();
100
- const doFetch = () => fetch(`${provider.baseUrl}/chat/completions`, {
101
- method: "POST",
102
- headers: {
103
- "Content-Type": "application/json",
104
- "Authorization": `Bearer ${provider.apiKey}`
105
- },
106
- body: JSON.stringify({
107
- model: provider.model,
108
- messages,
109
- temperature,
110
- max_tokens: maxTokens
111
- }),
112
- signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
113
- });
114
- let response;
115
- try {
116
- response = await doFetch();
117
- } catch (netErr) {
118
- attempts.push(`${provider.label}: network error (${netErr instanceof Error ? netErr.message : String(netErr)}) \u2014 retrying once`);
119
- await new Promise((r) => setTimeout(r, 5e3));
120
- lastRequestAt = Date.now();
121
- response = await doFetch();
122
- }
123
- if (!response.ok) {
124
- const body = await response.text().catch(() => "");
125
- const err = new LlmClientError(
126
- `[${provider.label}] LLM API error: ${response.status} ${response.statusText} \u2014 ${body.slice(0, 200)}`,
127
- response.status
128
- );
129
- if (response.status === 429) {
130
- const retryAfterRaw = response.headers.get("retry-after");
131
- const retryAfterMs = Math.min(
132
- 6e4,
133
- Math.max(15e3, (Number.isFinite(Number(retryAfterRaw)) ? Number(retryAfterRaw) : 20) * 1e3)
134
- );
135
- attempts.push(`${provider.label}: 429 rate-limited \u2014 backing off ${Math.round(retryAfterMs / 1e3)}s`);
136
- await new Promise((resolve) => setTimeout(resolve, retryAfterMs));
137
- lastRequestAt = Date.now();
138
- const retry = await fetch(`${provider.baseUrl}/chat/completions`, {
139
- method: "POST",
140
- headers,
141
- body: JSON.stringify(payload),
142
- signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
143
- });
144
- if (!retry.ok) throw err;
145
- const retryData = await retry.json();
146
- const retryContent2 = retryData.choices?.[0]?.message?.content || "";
147
- if (!retryContent2.trim()) throw err;
148
- callLog[recIdx] = { ...callLog[recIdx], ok: true };
149
- return {
150
- content: retryContent2,
151
- model: retryData.model || provider.model,
152
- provider: provider.label,
153
- attempts: attempts.slice(),
154
- usage: retryData.usage,
155
- finishReason: retryData.choices?.[0]?.finish_reason
156
- };
157
- }
158
- if (TRANSIENT.has(response.status)) {
159
- await new Promise((r) => setTimeout(r, 3e3));
160
- const retry = await fetch(`${provider.baseUrl}/chat/completions`, {
161
- signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
162
- method: "POST",
163
- headers: {
164
- "Content-Type": "application/json",
165
- "Authorization": `Bearer ${provider.apiKey}`
166
- },
167
- body: JSON.stringify({
168
- model: provider.model,
169
- messages,
170
- temperature,
171
- max_tokens: maxTokens
172
- })
173
- });
174
- if (!retry.ok) throw err;
175
- const retryData = await retry.json();
176
- const retryContent = retryData.choices?.[0]?.message?.content || "";
177
- callLog[recIdx] = { ...callLog[recIdx], ok: true };
178
- return {
179
- content: retryContent,
180
- model: retryData.model || provider.model,
181
- provider: provider.label,
182
- attempts: attempts.slice(),
183
- usage: retryData.usage,
184
- finishReason: retryData.choices?.[0]?.finish_reason
185
- };
186
- }
187
- throw err;
188
- }
189
- const data = await response.json();
190
- const wrappedError = data.error;
191
- if (wrappedError) {
192
- throw new LlmClientError("[" + provider.label + "] upstream error " + (wrappedError.code ?? "") + ": " + (wrappedError.message ?? "unknown"));
193
- }
194
- const content = data.choices?.[0]?.message?.content || "";
195
- if (!content.trim()) {
196
- throw new LlmClientError("[" + provider.label + "] empty completion returned");
197
- }
198
- return {
199
- content,
200
- model: data.model || provider.model,
201
- provider: provider.label,
202
- attempts: attempts.slice(),
203
- usage: data.usage,
204
- finishReason: data.choices?.[0]?.finish_reason
205
- };
206
- } catch (err) {
207
- const msg = err instanceof Error ? err.message : String(err);
208
- callLog[recIdx] = { ...callLog[recIdx], ok: false, error: msg.slice(0, 200) };
209
- attempts.push(`${provider.label}: ${msg}`);
210
- lastError = err instanceof LlmClientError ? err : new LlmClientError(`[${provider.label}] ${msg}`);
211
- }
212
- }
213
- if (round < MAX_CHAIN_ROUNDS) continue;
214
- throw lastError ?? new LlmClientError("All LLM providers failed (empty chain).");
215
- }
216
- throw lastError ?? new LlmClientError("All LLM providers failed after " + MAX_CHAIN_ROUNDS + " rounds.");
217
- }
218
- return { chat, model, callLog };
219
- }
220
- function parseJsonFromLlm(text) {
221
- let cleaned = text.trim();
222
- if (cleaned.startsWith("```json")) {
223
- cleaned = cleaned.slice(7);
224
- } else if (cleaned.startsWith("```")) {
225
- cleaned = cleaned.slice(3);
226
- }
227
- if (cleaned.endsWith("```")) {
228
- cleaned = cleaned.slice(0, -3);
229
- }
230
- cleaned = cleaned.trim();
231
- try {
232
- return JSON.parse(cleaned);
233
- } catch {
234
- const start = cleaned.indexOf("{");
235
- if (start >= 0) {
236
- let depth = 0;
237
- let inString = false;
238
- let escape = false;
239
- for (let i = start; i < cleaned.length; i++) {
240
- const ch = cleaned[i];
241
- if (escape) {
242
- escape = false;
243
- continue;
244
- }
245
- if (ch === "\\") {
246
- escape = true;
247
- continue;
248
- }
249
- if (ch === '"') {
250
- inString = !inString;
251
- continue;
252
- }
253
- if (inString) continue;
254
- if (ch === "{") depth++;
255
- if (ch === "}") {
256
- depth--;
257
- if (depth === 0) {
258
- try {
259
- return JSON.parse(cleaned.slice(start, i + 1));
260
- } catch {
261
- }
262
- }
263
- }
264
- }
265
- }
266
- throw new LlmClientError(`Failed to parse JSON from LLM response: ${cleaned.slice(0, 200)}`);
267
- }
268
- }
269
- async function llmChatJson(client, systemPrompt, userPrompt, options) {
270
- const result = await client.chat(
271
- [
272
- { role: "system", content: systemPrompt },
273
- { role: "user", content: userPrompt }
274
- ],
275
- options
276
- );
277
- return parseJsonFromLlm(result.content);
278
- }
279
-
280
9
  // src/graph/scaffoldExtractor.ts
281
10
  async function extractScaffold(options) {
282
11
  const { goal, techStack, fileList, llmConfig, sharedClient } = options;
@@ -375,7 +104,7 @@ async function extractProjectOverview(options) {
375
104
  (kw) => `- ${kw.keyword} (from ${kw.source})`
376
105
  );
377
106
  if (kwItems.length > 0) {
378
- keywordsAnchor = "\nAST Domain Keywords & Patterns (from static analysis \u2014 use as context anchor):\n" + kwItems.join("\n") + "\n";
107
+ keywordsAnchor = "\nDomain Keywords & Patterns (from regex-based static scan \u2014 use as context anchor):\n" + kwItems.join("\n") + "\n";
379
108
  }
380
109
  }
381
110
  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.";
@@ -1175,7 +904,110 @@ function techSignalHits(text) {
1175
904
  }
1176
905
  return hits;
1177
906
  }
1178
- function auditDepthLayers(concepts) {
907
+ var COMPLETABLE_VERBS = [
908
+ "build",
909
+ "create",
910
+ "implement",
911
+ "write",
912
+ "configure",
913
+ "add",
914
+ "use",
915
+ "apply",
916
+ "display",
917
+ "store",
918
+ "load",
919
+ "save",
920
+ "handle",
921
+ "parse",
922
+ "validate",
923
+ "test",
924
+ "register",
925
+ "compute",
926
+ "generate",
927
+ "print",
928
+ "format",
929
+ "convert",
930
+ "extend",
931
+ "define",
932
+ "execute",
933
+ "run",
934
+ "read",
935
+ "fetch",
936
+ "wrap",
937
+ "log",
938
+ "call",
939
+ "raise",
940
+ "install",
941
+ "import",
942
+ "export",
943
+ "declare",
944
+ "embed",
945
+ "model",
946
+ "represent",
947
+ "override",
948
+ "customize",
949
+ "enforce",
950
+ "mock",
951
+ "assert",
952
+ "query",
953
+ "index",
954
+ "cache",
955
+ "stream",
956
+ "split",
957
+ "join",
958
+ "strip",
959
+ "encode",
960
+ "decode",
961
+ "traverse",
962
+ "measure",
963
+ "evaluate",
964
+ "benchmark",
965
+ "diagnose",
966
+ "demonstrate",
967
+ "verify",
968
+ "organize",
969
+ "structure",
970
+ "choose",
971
+ "select",
972
+ "compare",
973
+ "distinguish",
974
+ "attach",
975
+ "annotate",
976
+ "expose",
977
+ "accept",
978
+ "manage",
979
+ "group",
980
+ "sort",
981
+ "filter",
982
+ "map",
983
+ "reduce",
984
+ "catch",
985
+ "throw",
986
+ "migrate",
987
+ "inspect",
988
+ "trace",
989
+ "parameterize",
990
+ "construct",
991
+ "design",
992
+ "capture",
993
+ // Completable implementation verbs present in production SIO output but
994
+ // missing from the original registry (2026-10-07 smoke test, swift kg):
995
+ "update",
996
+ "inject",
997
+ "compose",
998
+ "pass",
999
+ "modify",
1000
+ "connect",
1001
+ "remove",
1002
+ "transform",
1003
+ "trigger",
1004
+ "refresh",
1005
+ "persist",
1006
+ "observe",
1007
+ "arrange",
1008
+ "derive"
1009
+ ];
1010
+ function auditDepthLayers(concepts, transport) {
1179
1011
  return concepts.map((c) => {
1180
1012
  const ulo = (c.ulo ?? "").trim();
1181
1013
  const cio = (c.cio ?? "").trim();
@@ -1196,7 +1028,7 @@ function auditDepthLayers(concepts) {
1196
1028
  });
1197
1029
  const conceptKeywords = (c.keywords ?? []).map((k) => k.toLowerCase()).filter(Boolean);
1198
1030
  const sioOffConceptItems = conceptKeywords.length > 0 ? sioItems.filter((s) => !boundToConcept(s)) : [];
1199
- 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;
1031
+ const COMPLETABLE = new RegExp("\\b(?:" + COMPLETABLE_VERBS.join("|") + ")\\b", "i");
1200
1032
  const sioNotCompletable = sioItems.filter((s) => !COMPLETABLE.test(s) && !/`[^`]+`/.test(s));
1201
1033
  const density = (text) => {
1202
1034
  if (!text) return 0;
@@ -1236,7 +1068,8 @@ function auditDepthLayers(concepts) {
1236
1068
  clauseOk,
1237
1069
  codesOk,
1238
1070
  codeIssues,
1239
- passed
1071
+ passed,
1072
+ transport
1240
1073
  };
1241
1074
  });
1242
1075
  }
@@ -1271,14 +1104,14 @@ function excerpt(text, max) {
1271
1104
  const t = (text || "").trim();
1272
1105
  return t.length > max ? t.slice(0, max) + "..." : t;
1273
1106
  }
1274
- async function chatJson(client, system, user) {
1107
+ async function chatJson(client, system, user, transport) {
1275
1108
  const r = await client.chat(
1276
1109
  [{ role: "system", content: system }, { role: "user", content: user }],
1277
1110
  { temperature: 0.1, maxTokens: parseInt(process.env.KG_MAX_TOKENS || "65536", 10) }
1278
1111
  );
1279
1112
  return parseJsonFromLlm(r.content);
1280
1113
  }
1281
- var LO_BATCH_SIZE = 6;
1114
+ var LO_BATCH_SIZE = 10;
1282
1115
  function chunkConcepts(items, size) {
1283
1116
  const out = [];
1284
1117
  for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
@@ -1317,7 +1150,7 @@ async function authorDepthLayersHierarchically(client, concepts, techStack, warn
1317
1150
  for (const batch of chunkConcepts(concepts, LO_BATCH_SIZE)) {
1318
1151
  const batchList = batch.map((c) => "- " + c.id + " (" + c.name + ")" + (c.description ? ": " + excerpt(c.description, 160) : "")).join("\n");
1319
1152
  const batchUser = "Concepts:\n" + batchList + '\n\nReturn JSON:\n{"ulos":[{"concept_id":"C1","ulo":"..."}]}';
1320
- const uloRes = await chatJson(client, uloSystem, batchUser);
1153
+ const uloRes = await chatJson(client, uloSystem, batchUser, transportResultFrom("9router", client.model, { framingStripped: true }));
1321
1154
  for (const u of uloRes.ulos ?? []) {
1322
1155
  if (u.concept_id && u.ulo) uloByConcept[u.concept_id] = standardStatement(u.ulo);
1323
1156
  }
@@ -1356,7 +1189,7 @@ async function authorDepthLayersHierarchically(client, concepts, techStack, warn
1356
1189
  new Set(batch.map((c) => c.id));
1357
1190
  const batchUloContext = batch.map((c) => "- " + c.id + " (" + c.name + "): " + (uloByConcept[c.id] || "(missing)")).join("\n");
1358
1191
  const batchCioUser = "Approved ULOs:\n" + batchUloContext + '\n\nReturn JSON:\n{"cios":[{"concept_id":"C1","cio":"..."}]}';
1359
- const cioRes = await chatJson(client, cioSystem, batchCioUser);
1192
+ const cioRes = await chatJson(client, cioSystem, batchCioUser, transportResultFrom("9router", client.model, { framingStripped: true }));
1360
1193
  for (const x of cioRes.cios ?? []) {
1361
1194
  if (x.concept_id && x.cio) cioByConcept[x.concept_id] = standardStatement(x.cio);
1362
1195
  }
@@ -1381,15 +1214,7 @@ async function authorDepthLayersHierarchically(client, concepts, techStack, warn
1381
1214
  " context where it is used. Application/Analysis/Creation skills are assessed",
1382
1215
  " INDIRECTLY through completing these tasks \u2014 there is no fixed Bloom ceiling.",
1383
1216
  " MACHINE CHECK: every item MUST contain at least one ACTION VERB from this whitelist",
1384
- " (build, create, implement, write, configure, add, use, apply, display, store, load,",
1385
- " save, handle, parse, validate, test, register, compute, generate, print, format,",
1386
- " convert, extend, define, execute, run, read, fetch, wrap, log, call, raise, install,",
1387
- " import, export, declare, embed, model, represent, override, customize, enforce, mock,",
1388
- " assert, query, index, cache, stream, split, join, strip, encode, decode, traverse,",
1389
- " measure, evaluate, benchmark, diagnose, demonstrate, verify, organize, structure,",
1390
- " choose, select, compare, distinguish, attach, annotate, expose, accept, manage, group,",
1391
- " sort, filter, map, reduce, catch, throw, migrate, inspect, trace, parameterize,",
1392
- " construct, design, capture) \u2014 OR wrap the concrete construct in backticks.",
1217
+ " (" + COMPLETABLE_VERBS.join(", ") + ") \u2014 OR wrap the concrete construct in backticks.",
1393
1218
  " Items with abstract verbs (understand, learn, know, grasp, appreciate, explore) and",
1394
1219
  " no backtick construct FAIL the audit. Prefer starting the task phrase with the verb.",
1395
1220
  "3. 2-3 items per concept, each referencing a DIFFERENT concrete construct.",
@@ -1417,7 +1242,7 @@ async function authorDepthLayersHierarchically(client, concepts, techStack, warn
1417
1242
  "Return JSON:",
1418
1243
  '{"sios":[{"concept_id":"C1","sio":["...","..."]}]}'
1419
1244
  ].join("\n");
1420
- const sioRes = await chatJson(client, sioSystem, batchSioUser);
1245
+ const sioRes = await chatJson(client, sioSystem, batchSioUser, transportResultFrom("9router", client.model, { framingStripped: true }));
1421
1246
  for (const x of sioRes.sios ?? []) {
1422
1247
  if (x.concept_id && Array.isArray(x.sio)) sioByConcept[x.concept_id] = x.sio.map((item) => standardStatement(String(item)));
1423
1248
  }
@@ -1470,7 +1295,7 @@ async function authorDepthLayersHierarchically(client, concepts, techStack, warn
1470
1295
  "- CIO: HOW mechanism, technology-agnostic, more concrete than ULO, Marr 2-Language neutral.",
1471
1296
  "- SIO: keyword-bound completable implementation tasks naming real constructs of " + techStack + ".",
1472
1297
  "- 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).",
1473
- "- 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.",
1298
+ "- 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.",
1474
1299
  "",
1475
1300
  JSON_ONLY
1476
1301
  ].join("\n");
@@ -1518,7 +1343,7 @@ async function authorDepthLayersHierarchically(client, concepts, techStack, warn
1518
1343
  "Depth-layer contract audit failed after repair for: " + detail + " \u2014 regenerate or fix the prompt contract (fail-closed, no silent fallback)"
1519
1344
  );
1520
1345
  }
1521
- return { uloByConcept, cioByConcept, sioByConcept, audits, warnings };
1346
+ return { uloByConcept, cioByConcept, sioByConcept, audits, warnings, lastTransport: audits[0]?.transport };
1522
1347
  }
1523
1348
  function cell(v) {
1524
1349
  return (v || "").split(",").map((s) => s.trim()).filter(Boolean);
@@ -1661,7 +1486,7 @@ async function standardizeConcepts(opts) {
1661
1486
  " Adopt the tree's terminology. Only propose new when the tree genuinely has NO concept teaching that knowledge (broader AND narrower both fail).",
1662
1487
  ' 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.',
1663
1488
  " If between 1 and 3 tree concepts are CLOSE but not exact, list their codes in possible_tree_matches for human review.",
1664
- `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.`,
1489
+ `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.`,
1665
1490
  "Rules:",
1666
1491
  "- Judge by MEANING (definition + keywords), not by string similarity.",
1667
1492
  '- 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.',
@@ -1701,10 +1526,25 @@ async function standardizeConcepts(opts) {
1701
1526
  const tc = catalog.concepts.find((x) => x.code === d.tree_code);
1702
1527
  if (!tc) throw new Error("Concept " + c.id + " standardized to unknown tree code " + d.tree_code + " \u2014 fail-closed");
1703
1528
  const treeKw = new Set(tc.keywords.map((k) => k.toLowerCase()));
1704
- const adopted = (d.adopted_keywords || []).map((k) => String(k)).filter((k) => {
1705
- if (treeKw.has(k.toLowerCase())) return true;
1706
- throw new Error("Concept " + c.id + ' adopted keyword "' + k + '" not in tree concept ' + tc.code + " \u2014 fail-closed");
1707
- });
1529
+ const rawAdopted = (d.adopted_keywords || []).map((k) => String(k)).filter(Boolean);
1530
+ let adopted;
1531
+ if (tc.keywords.length === 0) {
1532
+ adopted = rawAdopted.filter((k) => (c.keywords ?? []).some((ck) => ck.toLowerCase() === k.toLowerCase()));
1533
+ if (adopted.length === 0) adopted = (c.keywords ?? []).slice(0, 8);
1534
+ warnings.push("Concept " + c.id + ": tree concept " + tc.code + " has no keyword registry \u2014 adopted " + adopted.length + " keyword(s) from the analysis concept");
1535
+ } else {
1536
+ const norm = (k) => k.toLowerCase().replace(/ies$/, "y").replace(/s$/, "");
1537
+ const treeKwNorm = new Set([...treeKw].map(norm));
1538
+ const dropped = [];
1539
+ adopted = rawAdopted.filter((k) => {
1540
+ if (treeKw.has(k.toLowerCase()) || treeKwNorm.has(norm(k))) return true;
1541
+ dropped.push(k);
1542
+ return false;
1543
+ });
1544
+ if (dropped.length > 0) {
1545
+ warnings.push("Concept " + c.id + ": dropped " + dropped.length + " adopted keyword(s) not in tree concept " + tc.code + " registry: " + dropped.join(", "));
1546
+ }
1547
+ }
1708
1548
  if (tc.keywords.length > 0 && adopted.length === 0) {
1709
1549
  throw new Error("Concept " + c.id + " standardized to " + tc.code + " without adopted_keywords \u2014 fail-closed");
1710
1550
  }
@@ -1799,7 +1639,7 @@ async function generateKnowledgeGraph(options) {
1799
1639
  log("STEP_1", `Parsed syllabus: ${parsedSyllabus.units.length} units, ${parsedSyllabus.allTopics.length} topics`);
1800
1640
  }
1801
1641
  const client = createLlmClient(llmConfig);
1802
- const maxSyllabusChars = parseInt(process.env.KG_MAX_SYLLABUS_CHARS || "16000", 10);
1642
+ const maxSyllabusChars = parseInt(process.env.KG_MAX_SYLLABUS_CHARS || "32000", 10);
1803
1643
  let truncatedSyllabus = "";
1804
1644
  if (syllabusText) {
1805
1645
  if (syllabusText.length > maxSyllabusChars) {
@@ -1989,23 +1829,32 @@ Return JSON:
1989
1829
  }
1990
1830
  }
1991
1831
  const idByTreeCode = new Map(concepts.filter((c) => c.tree_code).map((c) => [c.tree_code, c.id]));
1832
+ const unmappedTreePrereqs = /* @__PURE__ */ new Set();
1992
1833
  for (const c of concepts) {
1993
1834
  for (const tp of c.tree_prerequisites || []) {
1994
1835
  const target = idByTreeCode.get(tp);
1995
1836
  if (target && target !== c.id && !(c.prerequisites || []).includes(target)) {
1996
1837
  c.prerequisites = [...c.prerequisites || [], target];
1997
1838
  warnings.push("Prereq seeded from tree: " + c.id + " <- " + tp + " (" + target + ")");
1839
+ } else if (!target) {
1840
+ unmappedTreePrereqs.add(tp);
1998
1841
  }
1999
1842
  }
2000
1843
  }
1844
+ if (unmappedTreePrereqs.size > 0) {
1845
+ const shown = [...unmappedTreePrereqs].slice(0, 10).join(", ");
1846
+ warnings.push("Tree prereqs outside THIS analysis (no edge seeded, " + unmappedTreePrereqs.size + " codes): " + shown + (unmappedTreePrereqs.size > 10 ? ", ..." : ""));
1847
+ }
2001
1848
  log("STEP_2_5", "Standardized: " + std.decisions.filter((d) => d.decision === "standardized").length + ", proposed new: " + std.decisions.filter((d) => d.decision === "proposed_new").length);
1849
+ } else {
1850
+ warnings.push("No knowledgeTreePath provided \u2014 concepts are NOT standardized against the knowledge tree (all proposed_new)");
2002
1851
  }
2003
1852
  log("STEP_3", "Checking for prerequisite cycles...");
2004
1853
  const cycles = detectPrerequisiteCycles(concepts);
2005
1854
  if (cycles.length > 0) {
2006
1855
  log("STEP_3", `Found ${cycles.length} cycle(s) in prerequisite chain \u2014 breaking cycles`);
2007
1856
  for (const cycle of cycles) {
2008
- warnings.push(`Cycle detected: ${cycle.join(" \u2192 ")} \u2014 breaking at weakest edge`);
1857
+ warnings.push(`Cycle detected: ${cycle.join(" \u2192 ")} \u2014 removing the closing back-edge (last DFS edge; pedagogically arbitrary)`);
2009
1858
  }
2010
1859
  const { fixed, removed } = breakPrerequisiteCycles(concepts, cycles);
2011
1860
  for (let i = 0; i < concepts.length; i++) {
@@ -2420,6 +2269,6 @@ async function decomposePhases(args) {
2420
2269
  return phases;
2421
2270
  }
2422
2271
 
2423
- export { CIO_CODE_RE, CONCEPT_CODE_RE, LEARNER_CLAUSE, SIO_CODE_RE, ULO_CODE_RE, assignConceptCodes, auditDepthLayers, auditPhasePlan, auditSyllabusCoverage, breakPrerequisiteCycles, cioActionSlug, cioCode, conceptCodeFromName, createLlmClient, detectPrerequisiteCycles, escalateAndMapConcepts, extractFeatureSteps, extractFeatureStepsBatched, extractProjectOverview, extractScaffold, generateHybridGraph, generateKnowledgeGraph, llmChatJson, sioCode, standardStatement, summarizeProvenance, techTagFor, uloCode, verifyKnowledgeGraph, verifyProjectGraph };
2424
- //# sourceMappingURL=chunk-2OGNQUXC.mjs.map
2425
- //# sourceMappingURL=chunk-2OGNQUXC.mjs.map
2272
+ export { CIO_CODE_RE, CONCEPT_CODE_RE, LEARNER_CLAUSE, SIO_CODE_RE, ULO_CODE_RE, assignConceptCodes, auditDepthLayers, auditPhasePlan, auditSyllabusCoverage, authorDepthLayersHierarchically, breakPrerequisiteCycles, cioActionSlug, cioCode, conceptCodeFromName, detectPrerequisiteCycles, escalateAndMapConcepts, extractFeatureSteps, extractFeatureStepsBatched, extractProjectOverview, extractScaffold, generateHybridGraph, generateKnowledgeGraph, sioCode, standardStatement, techTagFor, uloCode, verifyKnowledgeGraph, verifyProjectGraph };
2273
+ //# sourceMappingURL=chunk-GVBDM746.mjs.map
2274
+ //# sourceMappingURL=chunk-GVBDM746.mjs.map