@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.
- 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-2OGNQUXC.mjs → chunk-4UCXYRM3.mjs} +152 -303
- package/dist/chunk-4UCXYRM3.mjs.map +1 -0
- 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-ERDTHXQA.mjs → chunk-H4W3YJLK.mjs} +10 -3
- package/dist/chunk-H4W3YJLK.mjs.map +1 -0
- package/dist/chunk-IBVXXVGE.mjs +14 -0
- package/dist/chunk-IBVXXVGE.mjs.map +1 -0
- package/dist/{chunk-5OMSYKQP.mjs → chunk-KHCZYLKM.mjs} +33 -18
- package/dist/chunk-KHCZYLKM.mjs.map +1 -0
- package/dist/{chunk-67GXK3ZE.mjs → chunk-WD3P5V3B.mjs} +3 -2
- package/dist/chunk-WD3P5V3B.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 +235 -57
- package/dist/graph/index.cjs.map +1 -1
- package/dist/graph/index.d.cts +9 -6
- package/dist/graph/index.d.ts +9 -6
- package/dist/graph/index.mjs +3 -1
- 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 +277 -68
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -8
- package/dist/index.d.ts +9 -8
- package/dist/index.mjs +11 -9
- 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/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
|
@@ -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 = "\
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
" (
|
|
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,
|
|
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":
|
|
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
|
|
1705
|
-
|
|
1706
|
-
|
|
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 || "
|
|
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
|
|
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,
|
|
2424
|
-
//# sourceMappingURL=chunk-
|
|
2425
|
-
//# sourceMappingURL=chunk-
|
|
2272
|
+
export { CIO_CODE_RE, CONCEPT_CODE_RE, LEARNER_CLAUSE, SIO_CODE_RE, ULO_CODE_RE, assignConceptCodes, auditDepthLayers, auditPhasePlan, auditSyllabusCoverage, breakPrerequisiteCycles, cioActionSlug, cioCode, conceptCodeFromName, detectPrerequisiteCycles, escalateAndMapConcepts, extractFeatureSteps, extractFeatureStepsBatched, extractProjectOverview, extractScaffold, generateHybridGraph, generateKnowledgeGraph, sioCode, standardStatement, techTagFor, uloCode, verifyKnowledgeGraph, verifyProjectGraph };
|
|
2273
|
+
//# sourceMappingURL=chunk-4UCXYRM3.mjs.map
|
|
2274
|
+
//# sourceMappingURL=chunk-4UCXYRM3.mjs.map
|