@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
package/dist/graph/index.cjs
CHANGED
|
@@ -4,7 +4,56 @@ var fs = require('fs');
|
|
|
4
4
|
var os = require('os');
|
|
5
5
|
var path = require('path');
|
|
6
6
|
|
|
7
|
+
// src/utils/llmTransportResult.ts
|
|
8
|
+
function transportResultFrom(provider, model, options) {
|
|
9
|
+
return {
|
|
10
|
+
provider,
|
|
11
|
+
model,
|
|
12
|
+
framingStripped: !!options?.framingStripped,
|
|
13
|
+
upstreamError: options?.upstreamError,
|
|
14
|
+
usage: options?.usage
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
7
18
|
// src/utils/llmClient.ts
|
|
19
|
+
async function responseJsonCompat(response) {
|
|
20
|
+
const text = await response.text();
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(text);
|
|
23
|
+
} catch {
|
|
24
|
+
const start = text.indexOf("{");
|
|
25
|
+
if (start < 0) {
|
|
26
|
+
throw new LlmClientError(`LLM API returned a non-JSON body: ${text.slice(0, 200)}`);
|
|
27
|
+
}
|
|
28
|
+
let depth = 0;
|
|
29
|
+
let inString = false;
|
|
30
|
+
let escape = false;
|
|
31
|
+
for (let i = start; i < text.length; i++) {
|
|
32
|
+
const ch = text[i];
|
|
33
|
+
if (escape) {
|
|
34
|
+
escape = false;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (ch === "\\") {
|
|
38
|
+
escape = true;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (ch === '"') {
|
|
42
|
+
inString = !inString;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (inString) continue;
|
|
46
|
+
if (ch === "{") depth++;
|
|
47
|
+
if (ch === "}") {
|
|
48
|
+
depth--;
|
|
49
|
+
if (depth === 0) {
|
|
50
|
+
return JSON.parse(text.slice(start, i + 1));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
throw new LlmClientError(`LLM API returned an unterminated JSON body: ${text.slice(0, 200)}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
8
57
|
var LlmClientError = class extends Error {
|
|
9
58
|
constructor(message, status, cause) {
|
|
10
59
|
super(message);
|
|
@@ -13,28 +62,22 @@ var LlmClientError = class extends Error {
|
|
|
13
62
|
this.name = "LlmClientError";
|
|
14
63
|
}
|
|
15
64
|
};
|
|
16
|
-
function
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (
|
|
31
|
-
|
|
32
|
-
if (or) {
|
|
33
|
-
const rest = chain.filter((c) => c !== or);
|
|
34
|
-
return [or, ...rest];
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return chain;
|
|
65
|
+
function build9routerProvider(model) {
|
|
66
|
+
const apiKey = process.env.NINEROUTER_API_KEY;
|
|
67
|
+
if (!apiKey) return null;
|
|
68
|
+
const baseUrl = (process.env.NINEROUTER_BASE_URL || "https://ai-router.orchable.app/v1").replace(/^["']|["']$/g, "").trim();
|
|
69
|
+
const label = "9router";
|
|
70
|
+
const modelName = (model || process.env.NINEROUTER_MODEL || "laguna-s-2.1").trim();
|
|
71
|
+
return { label, baseUrl, apiKey, model: modelName };
|
|
72
|
+
}
|
|
73
|
+
function resolve9routerChain(configModel) {
|
|
74
|
+
const primary = build9routerProvider(configModel);
|
|
75
|
+
if (!primary) return [];
|
|
76
|
+
const alt = process.env.NINEROUTER_MODEL_ALT && process.env.NINEROUTER_MODEL_ALT.trim();
|
|
77
|
+
if (!alt) return [primary];
|
|
78
|
+
const secondary = build9routerProvider(alt);
|
|
79
|
+
if (!secondary) return [primary];
|
|
80
|
+
return primary.model === secondary.model ? [primary] : [primary, secondary];
|
|
38
81
|
}
|
|
39
82
|
var MIN_REQUEST_INTERVAL_MS = 2500;
|
|
40
83
|
var requestIntervalMs = Number(process.env.LLM_MIN_REQUEST_INTERVAL_MS || MIN_REQUEST_INTERVAL_MS);
|
|
@@ -45,11 +88,11 @@ async function pacedDelay() {
|
|
|
45
88
|
lastRequestAt = Date.now();
|
|
46
89
|
}
|
|
47
90
|
function createLlmClient(config) {
|
|
48
|
-
const chain =
|
|
91
|
+
const chain = resolve9routerChain(config?.model);
|
|
49
92
|
const primary = chain[0];
|
|
50
93
|
if (!primary) {
|
|
51
94
|
throw new LlmClientError(
|
|
52
|
-
"No
|
|
95
|
+
"No 9router provider configured. Set NINEROUTER_API_KEY and NINEROUTER_MODEL (or the NINEROUTER_* env defaults expected by the kit)."
|
|
53
96
|
);
|
|
54
97
|
}
|
|
55
98
|
primary.apiKey;
|
|
@@ -131,17 +174,22 @@ function createLlmClient(config) {
|
|
|
131
174
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
132
175
|
});
|
|
133
176
|
if (!retry.ok) throw err;
|
|
134
|
-
const retryData = await retry
|
|
177
|
+
const retryData = await responseJsonCompat(retry);
|
|
135
178
|
const retryContent2 = retryData.choices?.[0]?.message?.content || "";
|
|
136
179
|
if (!retryContent2.trim()) throw err;
|
|
137
180
|
callLog[recIdx] = { ...callLog[recIdx], ok: true };
|
|
181
|
+
const transport2 = transportResultFrom(provider.label, retryData.model || provider.model, {
|
|
182
|
+
usage: retryData.usage,
|
|
183
|
+
framingStripped: true
|
|
184
|
+
});
|
|
138
185
|
return {
|
|
139
186
|
content: retryContent2,
|
|
140
187
|
model: retryData.model || provider.model,
|
|
141
188
|
provider: provider.label,
|
|
142
189
|
attempts: attempts.slice(),
|
|
143
190
|
usage: retryData.usage,
|
|
144
|
-
finishReason: retryData.choices?.[0]?.finish_reason
|
|
191
|
+
finishReason: retryData.choices?.[0]?.finish_reason,
|
|
192
|
+
_transport: transport2
|
|
145
193
|
};
|
|
146
194
|
}
|
|
147
195
|
if (TRANSIENT.has(response.status)) {
|
|
@@ -161,21 +209,26 @@ function createLlmClient(config) {
|
|
|
161
209
|
})
|
|
162
210
|
});
|
|
163
211
|
if (!retry.ok) throw err;
|
|
164
|
-
const retryData = await retry
|
|
212
|
+
const retryData = await responseJsonCompat(retry);
|
|
165
213
|
const retryContent = retryData.choices?.[0]?.message?.content || "";
|
|
166
214
|
callLog[recIdx] = { ...callLog[recIdx], ok: true };
|
|
215
|
+
const transport2 = transportResultFrom(provider.label, retryData.model || provider.model, {
|
|
216
|
+
usage: retryData.usage,
|
|
217
|
+
framingStripped: true
|
|
218
|
+
});
|
|
167
219
|
return {
|
|
168
220
|
content: retryContent,
|
|
169
221
|
model: retryData.model || provider.model,
|
|
170
222
|
provider: provider.label,
|
|
171
223
|
attempts: attempts.slice(),
|
|
172
224
|
usage: retryData.usage,
|
|
173
|
-
finishReason: retryData.choices?.[0]?.finish_reason
|
|
225
|
+
finishReason: retryData.choices?.[0]?.finish_reason,
|
|
226
|
+
_transport: transport2
|
|
174
227
|
};
|
|
175
228
|
}
|
|
176
229
|
throw err;
|
|
177
230
|
}
|
|
178
|
-
const data = await response
|
|
231
|
+
const data = await responseJsonCompat(response);
|
|
179
232
|
const wrappedError = data.error;
|
|
180
233
|
if (wrappedError) {
|
|
181
234
|
throw new LlmClientError("[" + provider.label + "] upstream error " + (wrappedError.code ?? "") + ": " + (wrappedError.message ?? "unknown"));
|
|
@@ -184,13 +237,18 @@ function createLlmClient(config) {
|
|
|
184
237
|
if (!content.trim()) {
|
|
185
238
|
throw new LlmClientError("[" + provider.label + "] empty completion returned");
|
|
186
239
|
}
|
|
240
|
+
const transport = transportResultFrom(provider.label, data.model || provider.model, {
|
|
241
|
+
usage: data.usage,
|
|
242
|
+
framingStripped: true
|
|
243
|
+
});
|
|
187
244
|
return {
|
|
188
245
|
content,
|
|
189
246
|
model: data.model || provider.model,
|
|
190
247
|
provider: provider.label,
|
|
191
248
|
attempts: attempts.slice(),
|
|
192
249
|
usage: data.usage,
|
|
193
|
-
finishReason: data.choices?.[0]?.finish_reason
|
|
250
|
+
finishReason: data.choices?.[0]?.finish_reason,
|
|
251
|
+
_transport: transport
|
|
194
252
|
};
|
|
195
253
|
} catch (err) {
|
|
196
254
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -364,7 +422,7 @@ async function extractProjectOverview(options) {
|
|
|
364
422
|
(kw) => `- ${kw.keyword} (from ${kw.source})`
|
|
365
423
|
);
|
|
366
424
|
if (kwItems.length > 0) {
|
|
367
|
-
keywordsAnchor = "\
|
|
425
|
+
keywordsAnchor = "\nDomain Keywords & Patterns (from regex-based static scan \u2014 use as context anchor):\n" + kwItems.join("\n") + "\n";
|
|
368
426
|
}
|
|
369
427
|
}
|
|
370
428
|
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.";
|
|
@@ -1380,7 +1438,110 @@ function techSignalHits(text) {
|
|
|
1380
1438
|
}
|
|
1381
1439
|
return hits;
|
|
1382
1440
|
}
|
|
1383
|
-
|
|
1441
|
+
var COMPLETABLE_VERBS = [
|
|
1442
|
+
"build",
|
|
1443
|
+
"create",
|
|
1444
|
+
"implement",
|
|
1445
|
+
"write",
|
|
1446
|
+
"configure",
|
|
1447
|
+
"add",
|
|
1448
|
+
"use",
|
|
1449
|
+
"apply",
|
|
1450
|
+
"display",
|
|
1451
|
+
"store",
|
|
1452
|
+
"load",
|
|
1453
|
+
"save",
|
|
1454
|
+
"handle",
|
|
1455
|
+
"parse",
|
|
1456
|
+
"validate",
|
|
1457
|
+
"test",
|
|
1458
|
+
"register",
|
|
1459
|
+
"compute",
|
|
1460
|
+
"generate",
|
|
1461
|
+
"print",
|
|
1462
|
+
"format",
|
|
1463
|
+
"convert",
|
|
1464
|
+
"extend",
|
|
1465
|
+
"define",
|
|
1466
|
+
"execute",
|
|
1467
|
+
"run",
|
|
1468
|
+
"read",
|
|
1469
|
+
"fetch",
|
|
1470
|
+
"wrap",
|
|
1471
|
+
"log",
|
|
1472
|
+
"call",
|
|
1473
|
+
"raise",
|
|
1474
|
+
"install",
|
|
1475
|
+
"import",
|
|
1476
|
+
"export",
|
|
1477
|
+
"declare",
|
|
1478
|
+
"embed",
|
|
1479
|
+
"model",
|
|
1480
|
+
"represent",
|
|
1481
|
+
"override",
|
|
1482
|
+
"customize",
|
|
1483
|
+
"enforce",
|
|
1484
|
+
"mock",
|
|
1485
|
+
"assert",
|
|
1486
|
+
"query",
|
|
1487
|
+
"index",
|
|
1488
|
+
"cache",
|
|
1489
|
+
"stream",
|
|
1490
|
+
"split",
|
|
1491
|
+
"join",
|
|
1492
|
+
"strip",
|
|
1493
|
+
"encode",
|
|
1494
|
+
"decode",
|
|
1495
|
+
"traverse",
|
|
1496
|
+
"measure",
|
|
1497
|
+
"evaluate",
|
|
1498
|
+
"benchmark",
|
|
1499
|
+
"diagnose",
|
|
1500
|
+
"demonstrate",
|
|
1501
|
+
"verify",
|
|
1502
|
+
"organize",
|
|
1503
|
+
"structure",
|
|
1504
|
+
"choose",
|
|
1505
|
+
"select",
|
|
1506
|
+
"compare",
|
|
1507
|
+
"distinguish",
|
|
1508
|
+
"attach",
|
|
1509
|
+
"annotate",
|
|
1510
|
+
"expose",
|
|
1511
|
+
"accept",
|
|
1512
|
+
"manage",
|
|
1513
|
+
"group",
|
|
1514
|
+
"sort",
|
|
1515
|
+
"filter",
|
|
1516
|
+
"map",
|
|
1517
|
+
"reduce",
|
|
1518
|
+
"catch",
|
|
1519
|
+
"throw",
|
|
1520
|
+
"migrate",
|
|
1521
|
+
"inspect",
|
|
1522
|
+
"trace",
|
|
1523
|
+
"parameterize",
|
|
1524
|
+
"construct",
|
|
1525
|
+
"design",
|
|
1526
|
+
"capture",
|
|
1527
|
+
// Completable implementation verbs present in production SIO output but
|
|
1528
|
+
// missing from the original registry (2026-10-07 smoke test, swift kg):
|
|
1529
|
+
"update",
|
|
1530
|
+
"inject",
|
|
1531
|
+
"compose",
|
|
1532
|
+
"pass",
|
|
1533
|
+
"modify",
|
|
1534
|
+
"connect",
|
|
1535
|
+
"remove",
|
|
1536
|
+
"transform",
|
|
1537
|
+
"trigger",
|
|
1538
|
+
"refresh",
|
|
1539
|
+
"persist",
|
|
1540
|
+
"observe",
|
|
1541
|
+
"arrange",
|
|
1542
|
+
"derive"
|
|
1543
|
+
];
|
|
1544
|
+
function auditDepthLayers(concepts, transport) {
|
|
1384
1545
|
return concepts.map((c) => {
|
|
1385
1546
|
const ulo = (c.ulo ?? "").trim();
|
|
1386
1547
|
const cio = (c.cio ?? "").trim();
|
|
@@ -1401,7 +1562,7 @@ function auditDepthLayers(concepts) {
|
|
|
1401
1562
|
});
|
|
1402
1563
|
const conceptKeywords = (c.keywords ?? []).map((k) => k.toLowerCase()).filter(Boolean);
|
|
1403
1564
|
const sioOffConceptItems = conceptKeywords.length > 0 ? sioItems.filter((s) => !boundToConcept(s)) : [];
|
|
1404
|
-
const COMPLETABLE =
|
|
1565
|
+
const COMPLETABLE = new RegExp("\\b(?:" + COMPLETABLE_VERBS.join("|") + ")\\b", "i");
|
|
1405
1566
|
const sioNotCompletable = sioItems.filter((s) => !COMPLETABLE.test(s) && !/`[^`]+`/.test(s));
|
|
1406
1567
|
const density = (text) => {
|
|
1407
1568
|
if (!text) return 0;
|
|
@@ -1441,7 +1602,8 @@ function auditDepthLayers(concepts) {
|
|
|
1441
1602
|
clauseOk,
|
|
1442
1603
|
codesOk,
|
|
1443
1604
|
codeIssues,
|
|
1444
|
-
passed
|
|
1605
|
+
passed,
|
|
1606
|
+
transport
|
|
1445
1607
|
};
|
|
1446
1608
|
});
|
|
1447
1609
|
}
|
|
@@ -1451,14 +1613,14 @@ function excerpt(text, max) {
|
|
|
1451
1613
|
const t = (text || "").trim();
|
|
1452
1614
|
return t.length > max ? t.slice(0, max) + "..." : t;
|
|
1453
1615
|
}
|
|
1454
|
-
async function chatJson(client, system, user) {
|
|
1616
|
+
async function chatJson(client, system, user, transport) {
|
|
1455
1617
|
const r = await client.chat(
|
|
1456
1618
|
[{ role: "system", content: system }, { role: "user", content: user }],
|
|
1457
1619
|
{ temperature: 0.1, maxTokens: parseInt(process.env.KG_MAX_TOKENS || "65536", 10) }
|
|
1458
1620
|
);
|
|
1459
1621
|
return parseJsonFromLlm(r.content);
|
|
1460
1622
|
}
|
|
1461
|
-
var LO_BATCH_SIZE =
|
|
1623
|
+
var LO_BATCH_SIZE = 10;
|
|
1462
1624
|
function chunkConcepts(items, size) {
|
|
1463
1625
|
const out = [];
|
|
1464
1626
|
for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
|
|
@@ -1497,7 +1659,7 @@ async function authorDepthLayersHierarchically(client, concepts, techStack, warn
|
|
|
1497
1659
|
for (const batch of chunkConcepts(concepts, LO_BATCH_SIZE)) {
|
|
1498
1660
|
const batchList = batch.map((c) => "- " + c.id + " (" + c.name + ")" + (c.description ? ": " + excerpt(c.description, 160) : "")).join("\n");
|
|
1499
1661
|
const batchUser = "Concepts:\n" + batchList + '\n\nReturn JSON:\n{"ulos":[{"concept_id":"C1","ulo":"..."}]}';
|
|
1500
|
-
const uloRes = await chatJson(client, uloSystem, batchUser);
|
|
1662
|
+
const uloRes = await chatJson(client, uloSystem, batchUser, transportResultFrom("9router", client.model, { framingStripped: true }));
|
|
1501
1663
|
for (const u of uloRes.ulos ?? []) {
|
|
1502
1664
|
if (u.concept_id && u.ulo) uloByConcept[u.concept_id] = standardStatement(u.ulo);
|
|
1503
1665
|
}
|
|
@@ -1536,7 +1698,7 @@ async function authorDepthLayersHierarchically(client, concepts, techStack, warn
|
|
|
1536
1698
|
new Set(batch.map((c) => c.id));
|
|
1537
1699
|
const batchUloContext = batch.map((c) => "- " + c.id + " (" + c.name + "): " + (uloByConcept[c.id] || "(missing)")).join("\n");
|
|
1538
1700
|
const batchCioUser = "Approved ULOs:\n" + batchUloContext + '\n\nReturn JSON:\n{"cios":[{"concept_id":"C1","cio":"..."}]}';
|
|
1539
|
-
const cioRes = await chatJson(client, cioSystem, batchCioUser);
|
|
1701
|
+
const cioRes = await chatJson(client, cioSystem, batchCioUser, transportResultFrom("9router", client.model, { framingStripped: true }));
|
|
1540
1702
|
for (const x of cioRes.cios ?? []) {
|
|
1541
1703
|
if (x.concept_id && x.cio) cioByConcept[x.concept_id] = standardStatement(x.cio);
|
|
1542
1704
|
}
|
|
@@ -1561,15 +1723,7 @@ async function authorDepthLayersHierarchically(client, concepts, techStack, warn
|
|
|
1561
1723
|
" context where it is used. Application/Analysis/Creation skills are assessed",
|
|
1562
1724
|
" INDIRECTLY through completing these tasks \u2014 there is no fixed Bloom ceiling.",
|
|
1563
1725
|
" MACHINE CHECK: every item MUST contain at least one ACTION VERB from this whitelist",
|
|
1564
|
-
" (
|
|
1565
|
-
" save, handle, parse, validate, test, register, compute, generate, print, format,",
|
|
1566
|
-
" convert, extend, define, execute, run, read, fetch, wrap, log, call, raise, install,",
|
|
1567
|
-
" import, export, declare, embed, model, represent, override, customize, enforce, mock,",
|
|
1568
|
-
" assert, query, index, cache, stream, split, join, strip, encode, decode, traverse,",
|
|
1569
|
-
" measure, evaluate, benchmark, diagnose, demonstrate, verify, organize, structure,",
|
|
1570
|
-
" choose, select, compare, distinguish, attach, annotate, expose, accept, manage, group,",
|
|
1571
|
-
" sort, filter, map, reduce, catch, throw, migrate, inspect, trace, parameterize,",
|
|
1572
|
-
" construct, design, capture) \u2014 OR wrap the concrete construct in backticks.",
|
|
1726
|
+
" (" + COMPLETABLE_VERBS.join(", ") + ") \u2014 OR wrap the concrete construct in backticks.",
|
|
1573
1727
|
" Items with abstract verbs (understand, learn, know, grasp, appreciate, explore) and",
|
|
1574
1728
|
" no backtick construct FAIL the audit. Prefer starting the task phrase with the verb.",
|
|
1575
1729
|
"3. 2-3 items per concept, each referencing a DIFFERENT concrete construct.",
|
|
@@ -1597,7 +1751,7 @@ async function authorDepthLayersHierarchically(client, concepts, techStack, warn
|
|
|
1597
1751
|
"Return JSON:",
|
|
1598
1752
|
'{"sios":[{"concept_id":"C1","sio":["...","..."]}]}'
|
|
1599
1753
|
].join("\n");
|
|
1600
|
-
const sioRes = await chatJson(client, sioSystem, batchSioUser);
|
|
1754
|
+
const sioRes = await chatJson(client, sioSystem, batchSioUser, transportResultFrom("9router", client.model, { framingStripped: true }));
|
|
1601
1755
|
for (const x of sioRes.sios ?? []) {
|
|
1602
1756
|
if (x.concept_id && Array.isArray(x.sio)) sioByConcept[x.concept_id] = x.sio.map((item) => standardStatement(String(item)));
|
|
1603
1757
|
}
|
|
@@ -1650,7 +1804,7 @@ async function authorDepthLayersHierarchically(client, concepts, techStack, warn
|
|
|
1650
1804
|
"- CIO: HOW mechanism, technology-agnostic, more concrete than ULO, Marr 2-Language neutral.",
|
|
1651
1805
|
"- SIO: keyword-bound completable implementation tasks naming real constructs of " + techStack + ".",
|
|
1652
1806
|
"- 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).",
|
|
1653
|
-
"- COMPLETABILITY RULE: every SIO item must contain an actionable verb (build, create,
|
|
1807
|
+
"- 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.",
|
|
1654
1808
|
"",
|
|
1655
1809
|
JSON_ONLY
|
|
1656
1810
|
].join("\n");
|
|
@@ -1698,7 +1852,7 @@ async function authorDepthLayersHierarchically(client, concepts, techStack, warn
|
|
|
1698
1852
|
"Depth-layer contract audit failed after repair for: " + detail + " \u2014 regenerate or fix the prompt contract (fail-closed, no silent fallback)"
|
|
1699
1853
|
);
|
|
1700
1854
|
}
|
|
1701
|
-
return { uloByConcept, cioByConcept, sioByConcept, audits, warnings };
|
|
1855
|
+
return { uloByConcept, cioByConcept, sioByConcept, audits, warnings, lastTransport: audits[0]?.transport };
|
|
1702
1856
|
}
|
|
1703
1857
|
function cell(v) {
|
|
1704
1858
|
return (v || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -1841,7 +1995,7 @@ async function standardizeConcepts(opts) {
|
|
|
1841
1995
|
" Adopt the tree's terminology. Only propose new when the tree genuinely has NO concept teaching that knowledge (broader AND narrower both fail).",
|
|
1842
1996
|
' 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.',
|
|
1843
1997
|
" If between 1 and 3 tree concepts are CLOSE but not exact, list their codes in possible_tree_matches for human review.",
|
|
1844
|
-
`For a "standardized" decision ALSO return "adopted_keywords":
|
|
1998
|
+
`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.`,
|
|
1845
1999
|
"Rules:",
|
|
1846
2000
|
"- Judge by MEANING (definition + keywords), not by string similarity.",
|
|
1847
2001
|
'- 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.',
|
|
@@ -1881,10 +2035,25 @@ async function standardizeConcepts(opts) {
|
|
|
1881
2035
|
const tc = catalog.concepts.find((x) => x.code === d.tree_code);
|
|
1882
2036
|
if (!tc) throw new Error("Concept " + c.id + " standardized to unknown tree code " + d.tree_code + " \u2014 fail-closed");
|
|
1883
2037
|
const treeKw = new Set(tc.keywords.map((k) => k.toLowerCase()));
|
|
1884
|
-
const
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
2038
|
+
const rawAdopted = (d.adopted_keywords || []).map((k) => String(k)).filter(Boolean);
|
|
2039
|
+
let adopted;
|
|
2040
|
+
if (tc.keywords.length === 0) {
|
|
2041
|
+
adopted = rawAdopted.filter((k) => (c.keywords ?? []).some((ck) => ck.toLowerCase() === k.toLowerCase()));
|
|
2042
|
+
if (adopted.length === 0) adopted = (c.keywords ?? []).slice(0, 8);
|
|
2043
|
+
warnings.push("Concept " + c.id + ": tree concept " + tc.code + " has no keyword registry \u2014 adopted " + adopted.length + " keyword(s) from the analysis concept");
|
|
2044
|
+
} else {
|
|
2045
|
+
const norm = (k) => k.toLowerCase().replace(/ies$/, "y").replace(/s$/, "");
|
|
2046
|
+
const treeKwNorm = new Set([...treeKw].map(norm));
|
|
2047
|
+
const dropped = [];
|
|
2048
|
+
adopted = rawAdopted.filter((k) => {
|
|
2049
|
+
if (treeKw.has(k.toLowerCase()) || treeKwNorm.has(norm(k))) return true;
|
|
2050
|
+
dropped.push(k);
|
|
2051
|
+
return false;
|
|
2052
|
+
});
|
|
2053
|
+
if (dropped.length > 0) {
|
|
2054
|
+
warnings.push("Concept " + c.id + ": dropped " + dropped.length + " adopted keyword(s) not in tree concept " + tc.code + " registry: " + dropped.join(", "));
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
1888
2057
|
if (tc.keywords.length > 0 && adopted.length === 0) {
|
|
1889
2058
|
throw new Error("Concept " + c.id + " standardized to " + tc.code + " without adopted_keywords \u2014 fail-closed");
|
|
1890
2059
|
}
|
|
@@ -1979,7 +2148,7 @@ async function generateKnowledgeGraph(options) {
|
|
|
1979
2148
|
log("STEP_1", `Parsed syllabus: ${parsedSyllabus.units.length} units, ${parsedSyllabus.allTopics.length} topics`);
|
|
1980
2149
|
}
|
|
1981
2150
|
const client = createLlmClient(llmConfig);
|
|
1982
|
-
const maxSyllabusChars = parseInt(process.env.KG_MAX_SYLLABUS_CHARS || "
|
|
2151
|
+
const maxSyllabusChars = parseInt(process.env.KG_MAX_SYLLABUS_CHARS || "32000", 10);
|
|
1983
2152
|
let truncatedSyllabus = "";
|
|
1984
2153
|
if (syllabusText) {
|
|
1985
2154
|
if (syllabusText.length > maxSyllabusChars) {
|
|
@@ -2169,23 +2338,32 @@ Return JSON:
|
|
|
2169
2338
|
}
|
|
2170
2339
|
}
|
|
2171
2340
|
const idByTreeCode = new Map(concepts.filter((c) => c.tree_code).map((c) => [c.tree_code, c.id]));
|
|
2341
|
+
const unmappedTreePrereqs = /* @__PURE__ */ new Set();
|
|
2172
2342
|
for (const c of concepts) {
|
|
2173
2343
|
for (const tp of c.tree_prerequisites || []) {
|
|
2174
2344
|
const target = idByTreeCode.get(tp);
|
|
2175
2345
|
if (target && target !== c.id && !(c.prerequisites || []).includes(target)) {
|
|
2176
2346
|
c.prerequisites = [...c.prerequisites || [], target];
|
|
2177
2347
|
warnings.push("Prereq seeded from tree: " + c.id + " <- " + tp + " (" + target + ")");
|
|
2348
|
+
} else if (!target) {
|
|
2349
|
+
unmappedTreePrereqs.add(tp);
|
|
2178
2350
|
}
|
|
2179
2351
|
}
|
|
2180
2352
|
}
|
|
2353
|
+
if (unmappedTreePrereqs.size > 0) {
|
|
2354
|
+
const shown = [...unmappedTreePrereqs].slice(0, 10).join(", ");
|
|
2355
|
+
warnings.push("Tree prereqs outside THIS analysis (no edge seeded, " + unmappedTreePrereqs.size + " codes): " + shown + (unmappedTreePrereqs.size > 10 ? ", ..." : ""));
|
|
2356
|
+
}
|
|
2181
2357
|
log("STEP_2_5", "Standardized: " + std.decisions.filter((d) => d.decision === "standardized").length + ", proposed new: " + std.decisions.filter((d) => d.decision === "proposed_new").length);
|
|
2358
|
+
} else {
|
|
2359
|
+
warnings.push("No knowledgeTreePath provided \u2014 concepts are NOT standardized against the knowledge tree (all proposed_new)");
|
|
2182
2360
|
}
|
|
2183
2361
|
log("STEP_3", "Checking for prerequisite cycles...");
|
|
2184
2362
|
const cycles = detectPrerequisiteCycles(concepts);
|
|
2185
2363
|
if (cycles.length > 0) {
|
|
2186
2364
|
log("STEP_3", `Found ${cycles.length} cycle(s) in prerequisite chain \u2014 breaking cycles`);
|
|
2187
2365
|
for (const cycle of cycles) {
|
|
2188
|
-
warnings.push(`Cycle detected: ${cycle.join(" \u2192 ")} \u2014
|
|
2366
|
+
warnings.push(`Cycle detected: ${cycle.join(" \u2192 ")} \u2014 removing the closing back-edge (last DFS edge; pedagogically arbitrary)`);
|
|
2189
2367
|
}
|
|
2190
2368
|
const { fixed, removed } = breakPrerequisiteCycles(concepts, cycles);
|
|
2191
2369
|
for (let i = 0; i < concepts.length; i++) {
|