progmune-runtime 2.0.4 โ 2.1.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/.mcp.json +11 -0
- package/.progmune_allowlist +50 -0
- package/.test_report/test_report.md +87 -0
- package/Dockerfile +2 -10
- package/FAQ.md +167 -0
- package/README.md +108 -54
- package/demo-project/auth.ts +55 -0
- package/demo-project/tsconfig.json +8 -0
- package/dist/ab-stats.js +11 -0
- package/dist/acl-breakdown.js +13 -0
- package/dist/action-runtime.js +1 -0
- package/dist/all-sessions.js +11 -0
- package/dist/antibody-stats.js +11 -0
- package/dist/audit.js +222 -0
- package/dist/benchmark-count.js +7 -0
- package/dist/benchmark-full.js +17 -0
- package/dist/benchmark-pass-rate.js +54 -0
- package/dist/benchmark-report.js +67 -0
- package/dist/benchmark-save.js +62 -0
- package/dist/benchmark-status.js +15 -0
- package/dist/branch-ledger.js +393 -0
- package/dist/branch-tree-count.js +14 -0
- package/dist/check.js +506 -0
- package/dist/common-fixpath.js +12 -0
- package/dist/constraint-types.js +12 -0
- package/dist/deterministic-replay.js +283 -0
- package/dist/emitter.js +143 -12
- package/dist/exec-metrics.js +11 -0
- package/dist/execute.js +251 -0
- package/dist/extract-ir.js +592 -5
- package/dist/failure-collector.js +163 -0
- package/dist/failure-corpus.js +507 -35
- package/dist/failure-report.js +11 -0
- package/dist/failures.js +11 -0
- package/dist/fast-path-hits.js +13 -0
- package/dist/feedback.js +10 -3
- package/dist/file-lock.js +82 -0
- package/dist/find-session.js +10 -0
- package/dist/fingerprint-list.js +15 -0
- package/dist/gen-history-log.js +13 -0
- package/dist/generate.js +2 -1
- package/dist/generate_500.js +4 -2
- package/dist/genome.js +11 -0
- package/dist/heatmap-data.js +11 -0
- package/dist/heatmap.js +11 -0
- package/dist/immune-reporter.js +34 -22
- package/dist/ir-utils.js +18 -0
- package/dist/learned.js +11 -0
- package/dist/ledger-registry.js +252 -0
- package/dist/llm.js +46 -2
- package/dist/load-benchmarks.js +47 -0
- package/dist/main.js +2 -1
- package/dist/mcp-server.mjs +445 -43
- package/dist/memory-layer.js +54 -13
- package/dist/metrics.js +11 -0
- package/dist/obs-web.js +561 -0
- package/dist/p0_ssg_demo.js +255 -40
- package/dist/planner.js +996 -80
- package/dist/protocol-registry.js +112 -0
- package/dist/recent-session.js +12 -0
- package/dist/repair-proposal.js +363 -0
- package/dist/runtime-invariants.js +170 -0
- package/dist/runtime-types.js +117 -0
- package/dist/runtime.js +1 -0
- package/dist/search-planner.js +39 -9
- package/dist/semantic-snapshot.js +157 -0
- package/dist/semantic-trace.js +1497 -0
- package/dist/semantic-validator.js +3 -2
- package/dist/semantic_guard_test.js +2 -1
- package/dist/session-utils.js +19 -0
- package/dist/sessions.js +11 -0
- package/dist/ssg-validator.js +666 -20
- package/dist/svl-distribution.js +11 -0
- package/dist/terminal-status.js +11 -0
- package/dist/test_failure_corpus.js +3 -1
- package/dist/token-savings.js +11 -0
- package/dist/total-repairs.js +12 -0
- package/dist/unresolved-count.js +12 -0
- package/dist/utils.js +2 -0
- package/dist/valid-fingerprints.js +13 -0
- package/dist/validator.js +118 -60
- package/dist/verify-fps.js +11 -0
- package/dist/verify-ledgers.js +11 -0
- package/docs/whitepaper-style.css +77 -0
- package/docs/whitepaper-v2.1.md +609 -0
- package/docs/whitepaper-v2.2.md +1064 -0
- package/docs/whitepaper-v2.2.pdf +0 -0
- package/fly.toml +1 -1
- package/package.json +13 -4
- package/protocols.json +131 -11
- package/public/dashboard.html +119 -0
- package/server/hub.js +84 -12
- package/test/replay-golden/sess_1780063202050_mgeld.json +9 -0
- package/test/replay-golden/sess_1780064032560_gocld.json +354 -0
- package/test/replay-golden/sess_1780064413331_s2709.json +606 -0
- package/test/replay-golden/sess_1780064792710_y3avo.json +614 -0
- package/test/replay-golden.ts +84 -0
- package/test_benchmark.js +165 -0
- package/test_comprehensive.mjs +638 -0
- package/test_concurrency.js +129 -0
- package/test_ir_robustness.js +85 -0
- package/test_semantic_contracts.js +269 -0
- package/test_ssg_stress.js +156 -0
- package/test_svl3.js +58 -0
- package/tsconfig.json +1 -1
- package/.env.example +0 -3
- package/.progmune_memory/episodic.json +0 -186
- package/.progmune_memory/fingerprints.json +0 -7
- package/.progmune_memory/opt_in.json +0 -4
- package/immune_hub_data/2026-05-14.json +0 -50
package/dist/extract-ir.js
CHANGED
|
@@ -34,9 +34,104 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.extractIR = extractIR;
|
|
37
|
+
exports.extractIRWithTypes = extractIRWithTypes;
|
|
37
38
|
const ts_morph_1 = require("ts-morph");
|
|
38
39
|
const path = __importStar(require("path"));
|
|
39
40
|
const fs = __importStar(require("fs"));
|
|
41
|
+
const ts = __importStar(require("typescript"));
|
|
42
|
+
/** ไป JSDoc ๆณจ้ไธญ่งฃๆ capability ๆณจ่งฃ (@purpose, @tags, @requires, @produces) */
|
|
43
|
+
function parseCapabilityFromJSDoc(node) {
|
|
44
|
+
const jsdocs = node.getJsDocs?.();
|
|
45
|
+
if (!jsdocs || jsdocs.length === 0)
|
|
46
|
+
return {};
|
|
47
|
+
const result = {};
|
|
48
|
+
for (const doc of jsdocs) {
|
|
49
|
+
// @purpose: full description text (all lines before any @tag)
|
|
50
|
+
const fullText = doc.getFullText?.() || "";
|
|
51
|
+
// Extract description: everything between "/**" and the first "@tag"
|
|
52
|
+
const descMatch = fullText.match(/\/\*\*\s*\n?\s*\*?\s*([^@]*)/);
|
|
53
|
+
if (descMatch) {
|
|
54
|
+
const desc = descMatch[1].replace(/\n\s*\*\s*/g, " ").trim();
|
|
55
|
+
if (desc && !desc.startsWith("@")) {
|
|
56
|
+
result.purpose = desc;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Fallback: use getComment()
|
|
60
|
+
if (!result.purpose) {
|
|
61
|
+
const comment = doc.getComment?.() || "";
|
|
62
|
+
if (comment && !comment.startsWith("@")) {
|
|
63
|
+
result.purpose = comment.split("\n")[0].trim();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// @tags from ts-morph tag system
|
|
67
|
+
const tsTags = doc.getTags?.();
|
|
68
|
+
if (tsTags) {
|
|
69
|
+
for (const t of tsTags) {
|
|
70
|
+
const tn = t.getTagName?.();
|
|
71
|
+
if (tn === "tags" || tn === "tag") {
|
|
72
|
+
const val = t.getCommentText?.() || "";
|
|
73
|
+
result.tags = val.split(/[,\s]+/).map((s) => s.trim()).filter(Boolean);
|
|
74
|
+
}
|
|
75
|
+
if (tn === "requires") {
|
|
76
|
+
const val = t.getCommentText?.() || "";
|
|
77
|
+
if (!result.requires)
|
|
78
|
+
result.requires = [];
|
|
79
|
+
result.requires.push(...val.split(/[,\s]+/).map((s) => s.trim()).filter(Boolean));
|
|
80
|
+
}
|
|
81
|
+
if (tn === "produces") {
|
|
82
|
+
const val = t.getCommentText?.() || "";
|
|
83
|
+
if (!result.produces)
|
|
84
|
+
result.produces = [];
|
|
85
|
+
result.produces.push(...val.split(/[,\s]+/).map((s) => s.trim()).filter(Boolean));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
/** Auto-derive tags from function's source file name */
|
|
93
|
+
function deriveTagsFromFile(filePath) {
|
|
94
|
+
const name = filePath.replace(/\.ts$/, "").replace(/^src\//, "");
|
|
95
|
+
const tags = name.split(/[\/\-]/).filter(t => t.length > 2 && t !== "src");
|
|
96
|
+
return [...new Set(tags)];
|
|
97
|
+
}
|
|
98
|
+
/** ไป JSDoc ๆณจ้ไธญ่งฃๆ @protocol ๆณจ่งฃ */
|
|
99
|
+
function parseProtocolFromJSDoc(node) {
|
|
100
|
+
const jsdocs = node.getJsDocs?.();
|
|
101
|
+
if (!jsdocs || jsdocs.length === 0)
|
|
102
|
+
return undefined;
|
|
103
|
+
for (const doc of jsdocs) {
|
|
104
|
+
const tags = doc.getTags?.();
|
|
105
|
+
if (!tags)
|
|
106
|
+
continue;
|
|
107
|
+
for (const tag of tags) {
|
|
108
|
+
const tagName = tag.getTagName?.();
|
|
109
|
+
if (tagName !== 'protocol')
|
|
110
|
+
continue;
|
|
111
|
+
const text = tag.getCommentText?.() || '';
|
|
112
|
+
// ่งฃๆๆ ผๅผ: namespace=file pre_states=["A","B"] post_states=["C"] invalidate=["A"]
|
|
113
|
+
try {
|
|
114
|
+
const nsMatch = text.match(/namespace\s*=\s*(\w+)/);
|
|
115
|
+
const preMatch = text.match(/pre_states\s*=\s*\[([^\]]*)\]/);
|
|
116
|
+
const postMatch = text.match(/post_states\s*=\s*\[([^\]]*)\]/);
|
|
117
|
+
const invMatch = text.match(/invalidate\s*=\s*\[([^\]]*)\]/);
|
|
118
|
+
if (!preMatch || !postMatch)
|
|
119
|
+
return undefined;
|
|
120
|
+
const namespace = nsMatch ? nsMatch[1] : undefined;
|
|
121
|
+
const pre_states = preMatch[1].split(',').map((s) => s.trim().replace(/["']/g, '')).filter(Boolean);
|
|
122
|
+
const post_states = postMatch[1].split(',').map((s) => s.trim().replace(/["']/g, '')).filter(Boolean);
|
|
123
|
+
const invalidate = invMatch
|
|
124
|
+
? invMatch[1].split(',').map((s) => s.trim().replace(/["']/g, '')).filter(Boolean)
|
|
125
|
+
: undefined;
|
|
126
|
+
return { pre_states, post_states, invalidate, namespace };
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
40
135
|
// ่ทๅ็ฑปๅ่็น็็ปๆๅๆ่ฟฐ
|
|
41
136
|
function getTypeDetail(typeNode) {
|
|
42
137
|
if (!typeNode)
|
|
@@ -79,6 +174,18 @@ function getReturnTypeDetail(func) {
|
|
|
79
174
|
const typeNode = func.getReturnTypeNode?.();
|
|
80
175
|
return typeNode ? getTypeDetail(typeNode) : "";
|
|
81
176
|
}
|
|
177
|
+
/** Derive input types from params (auto-capability graph). */
|
|
178
|
+
function deriveInputs(params) {
|
|
179
|
+
return params.map(p => {
|
|
180
|
+
const t = (p.type || "any").replace(/\[\]$/, "").replace(/<[^>]*>/g, "");
|
|
181
|
+
return t.split("|")[0].trim();
|
|
182
|
+
}).filter(t => t !== "any" && t !== "void");
|
|
183
|
+
}
|
|
184
|
+
/** Derive output types from return type (auto-capability graph). */
|
|
185
|
+
function deriveOutputs(returnType) {
|
|
186
|
+
const t = returnType.replace(/\[\]$/, "").replace(/<[^>]*>/g, "").split("|")[0].trim();
|
|
187
|
+
return (t === "void" || t === "any") ? [] : [t];
|
|
188
|
+
}
|
|
82
189
|
function extractDirectCalls(func) {
|
|
83
190
|
const body = func.getBody();
|
|
84
191
|
if (!body)
|
|
@@ -97,7 +204,19 @@ function extractDirectCalls(func) {
|
|
|
97
204
|
});
|
|
98
205
|
return [...new Set(calls)];
|
|
99
206
|
}
|
|
207
|
+
/**
|
|
208
|
+
* ไป TypeScript ้กน็ฎๆๅ IR๏ผๅฝๆฐ็ญพๅใๅๆฐใ่ฟๅๅผใๅ่ฎฎๆณจ่งฃ๏ผใ
|
|
209
|
+
* @protocol namespace=dev_pipeline pre_states=[] post_states=["IR_EXTRACTED"] invalidate=["IR_STALE"]
|
|
210
|
+
*/
|
|
211
|
+
/** @requires PROJECT_PATH @produces IR_FUNCTIONS */
|
|
212
|
+
/** @requires PROJECT_PATH @produces IR_FUNCTIONS */
|
|
100
213
|
function extractIR(projectRoot) {
|
|
214
|
+
return extractIRWithTypes(projectRoot).functions;
|
|
215
|
+
}
|
|
216
|
+
/** Extract both functions and typeโfile mapping. */
|
|
217
|
+
/** @requires PROJECT_PATH @produces IR_WITH_TYPES */
|
|
218
|
+
/** @requires PROJECT_PATH @produces IR_WITH_TYPES */
|
|
219
|
+
function extractIRWithTypes(projectRoot) {
|
|
101
220
|
const absRoot = path.resolve(projectRoot);
|
|
102
221
|
const project = new ts_morph_1.Project({
|
|
103
222
|
tsConfigFilePath: path.join(absRoot, "tsconfig.json"),
|
|
@@ -124,11 +243,478 @@ function extractIR(projectRoot) {
|
|
|
124
243
|
returnTypeDetail: getReturnTypeDetail(f),
|
|
125
244
|
file: relPath,
|
|
126
245
|
calls: extractDirectCalls(f),
|
|
246
|
+
exported: f.isExported(),
|
|
247
|
+
inputs: deriveInputs(f.getParameters().map(p => ({ name: p.getName(), type: p.getTypeNode()?.getText() || "any" }))),
|
|
248
|
+
outputs: deriveOutputs(f.getReturnTypeNode()?.getText() || "any"),
|
|
249
|
+
protocol: parseProtocolFromJSDoc(f),
|
|
250
|
+
...parseCapabilityFromJSDoc(f),
|
|
127
251
|
});
|
|
128
252
|
}
|
|
129
|
-
//
|
|
253
|
+
// ๆๅ็ฎญๅคดๅฝๆฐ๏ผconst fn = () => {}, export const fn = () => {} ็ญ๏ผ
|
|
254
|
+
for (const vd of sf.getVariableDeclarations()) {
|
|
255
|
+
const init = vd.getInitializer();
|
|
256
|
+
if (!init)
|
|
257
|
+
continue;
|
|
258
|
+
// ็ดๆฅ็ฎญๅคดๅฝๆฐ: const fn = () => {}
|
|
259
|
+
if (ts_morph_1.Node.isArrowFunction(init)) {
|
|
260
|
+
const name = vd.getName();
|
|
261
|
+
funcs.push({
|
|
262
|
+
name,
|
|
263
|
+
params: init.getParameters().map(p => ({
|
|
264
|
+
name: p.getName(),
|
|
265
|
+
type: getParamType(p),
|
|
266
|
+
typeDetail: getParamTypeDetail(p),
|
|
267
|
+
})),
|
|
268
|
+
returnType: getReturnType(init),
|
|
269
|
+
returnTypeDetail: getReturnTypeDetail(init),
|
|
270
|
+
file: relPath,
|
|
271
|
+
exported: vd.isExported(),
|
|
272
|
+
calls: extractDirectCalls(init),
|
|
273
|
+
protocol: parseProtocolFromJSDoc(vd),
|
|
274
|
+
...parseCapabilityFromJSDoc(vd),
|
|
275
|
+
});
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
// ๅ
่ฃ
็็ฎญๅคดๅฝๆฐ: const fn = debounce(() => {})
|
|
279
|
+
if (ts_morph_1.Node.isCallExpression(init)) {
|
|
280
|
+
for (const arg of init.getArguments()) {
|
|
281
|
+
if (ts_morph_1.Node.isArrowFunction(arg)) {
|
|
282
|
+
const name = vd.getName();
|
|
283
|
+
funcs.push({
|
|
284
|
+
name,
|
|
285
|
+
params: arg.getParameters().map(p => ({
|
|
286
|
+
name: p.getName(),
|
|
287
|
+
type: getParamType(p),
|
|
288
|
+
typeDetail: getParamTypeDetail(p),
|
|
289
|
+
})),
|
|
290
|
+
returnType: getReturnType(arg),
|
|
291
|
+
returnTypeDetail: getReturnTypeDetail(arg),
|
|
292
|
+
file: relPath,
|
|
293
|
+
exported: vd.isExported(),
|
|
294
|
+
calls: extractDirectCalls(arg),
|
|
295
|
+
protocol: parseProtocolFromJSDoc(vd),
|
|
296
|
+
});
|
|
297
|
+
break; // ๅชๅ็ฌฌไธไธช็ฎญๅคดๅฝๆฐๅๆฐ
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
// Extract class methods: ts-morph getFunctions() excludes class members
|
|
302
|
+
for (const cls of sf.getClasses()) {
|
|
303
|
+
const cn = cls.getName();
|
|
304
|
+
if (!cn)
|
|
305
|
+
continue;
|
|
306
|
+
for (const m of cls.getMethods()) {
|
|
307
|
+
const mn = m.getName();
|
|
308
|
+
if (!mn)
|
|
309
|
+
continue;
|
|
310
|
+
funcs.push({
|
|
311
|
+
name: `${cn}.${mn}`,
|
|
312
|
+
params: m.getParameters().map((p) => ({ name: p.getName(), type: getParamType(p), typeDetail: getParamTypeDetail(p) })),
|
|
313
|
+
returnType: m.getReturnTypeNode?.()?.getText?.() || "any",
|
|
314
|
+
returnTypeDetail: m.getReturnTypeNode?.()?.getText?.() || "any",
|
|
315
|
+
file: relPath,
|
|
316
|
+
exported: vd.isExported(), calls: [],
|
|
317
|
+
...parseCapabilityFromJSDoc(m),
|
|
318
|
+
protocol: parseProtocolFromJSDoc(m),
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
325
|
+
// Phase 5: Dynamic external function resolution
|
|
326
|
+
// Replaces hardcoded knownExternals
|
|
327
|
+
// includes: npm packages (.d.ts via ts.resolveModuleName), Node.js built-ins (@types/node)
|
|
328
|
+
// fallback: knownExternals registry for unresolved functions
|
|
329
|
+
// final fallback: any type with empty params with ts-morph + ts.resolveModuleName
|
|
330
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
331
|
+
const declaredNames = new Set(funcs.map(f => f.name));
|
|
332
|
+
// Collect imports for dynamic resolution
|
|
333
|
+
const externalFuncs = new Map();
|
|
334
|
+
// Also track namespace imports: import * as X from 'module' โ X.method
|
|
335
|
+
const namespaceImports = new Map(); // X โ resolved source file
|
|
336
|
+
for (const sf of project.getSourceFiles()) {
|
|
337
|
+
if (sf.getFilePath().includes('node_modules'))
|
|
338
|
+
continue;
|
|
339
|
+
for (const imp of sf.getImportDeclarations()) {
|
|
340
|
+
const mod = imp.getModuleSpecifierValue();
|
|
341
|
+
// Named imports: import { X } from 'mod'
|
|
342
|
+
for (const ni of imp.getNamedImports()) {
|
|
343
|
+
const name = ni.getName();
|
|
344
|
+
if (declaredNames.has(name))
|
|
345
|
+
continue;
|
|
346
|
+
if (externalFuncs.has(name))
|
|
347
|
+
continue;
|
|
348
|
+
try {
|
|
349
|
+
const resolved = imp.getModuleSpecifierSourceFile();
|
|
350
|
+
if (resolved) {
|
|
351
|
+
const sig = extractSignatureFromFile(name, resolved);
|
|
352
|
+
if (sig) {
|
|
353
|
+
externalFuncs.set(name, sig);
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
const tsResult = ts.resolveModuleName(mod, sf.getFilePath(), {}, ts.sys);
|
|
358
|
+
const resolvedPath = tsResult.resolvedModule?.resolvedFileName;
|
|
359
|
+
if (resolvedPath && fs.existsSync(resolvedPath)) {
|
|
360
|
+
const sig = extractSignatureFromDts(name, resolvedPath, project);
|
|
361
|
+
if (sig) {
|
|
362
|
+
externalFuncs.set(name, sig);
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
catch { }
|
|
368
|
+
}
|
|
369
|
+
// Namespace imports: import * as X from 'mod'
|
|
370
|
+
const nsImport = imp.getNamespaceImport();
|
|
371
|
+
if (nsImport) {
|
|
372
|
+
try {
|
|
373
|
+
const resolved = imp.getModuleSpecifierSourceFile();
|
|
374
|
+
if (resolved) {
|
|
375
|
+
namespaceImports.set(nsImport.getText(), resolved);
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
const tsResult = ts.resolveModuleName(mod, sf.getFilePath(), {}, ts.sys);
|
|
379
|
+
const resolvedPath = tsResult.resolvedModule?.resolvedFileName;
|
|
380
|
+
if (resolvedPath && fs.existsSync(resolvedPath)) {
|
|
381
|
+
const dtsFile = project.addSourceFileAtPathIfExists(resolvedPath);
|
|
382
|
+
if (dtsFile)
|
|
383
|
+
namespaceImports.set(nsImport.getText(), dtsFile);
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
// Node.js built-in: try @types/node
|
|
387
|
+
const nodeTypesPath = path.join(absRoot, "node_modules/@types/node", mod + ".d.ts");
|
|
388
|
+
if (fs.existsSync(nodeTypesPath)) {
|
|
389
|
+
const nodeDts = project.addSourceFileAtPathIfExists(nodeTypesPath);
|
|
390
|
+
if (nodeDts)
|
|
391
|
+
namespaceImports.set(nsImport.getText(), nodeDts);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
catch { }
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
// Resolve namespace calls: fs.readFileSync โ look up 'readFileSync' in 'fs' source file
|
|
401
|
+
function tryResolveFromNamespace(callName, prefix) {
|
|
402
|
+
const sourceFile = namespaceImports.get(prefix);
|
|
403
|
+
if (!sourceFile)
|
|
404
|
+
return false;
|
|
405
|
+
const sig = extractSignatureFromFile(callName, sourceFile);
|
|
406
|
+
if (sig) {
|
|
407
|
+
externalFuncs.set(callName, sig);
|
|
408
|
+
return true;
|
|
409
|
+
}
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
// Collect undeclared calls (functions used but not declared and not resolved above)
|
|
413
|
+
const allCalls = new Set();
|
|
414
|
+
for (const f of funcs) {
|
|
415
|
+
for (const c of f.calls) {
|
|
416
|
+
allCalls.add(c);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
// JS ๅ
็ฝฎๆนๆณๅ TypeScript ่ฏญๆณ่็น๏ผไธไฝไธบๅค้จๅฝๆฐๆด้ฒ
|
|
420
|
+
const ignoredBuiltins = new Set([
|
|
421
|
+
"map", "filter", "reduce", "forEach", "find", "some", "every",
|
|
422
|
+
"push", "pop", "shift", "unshift", "slice", "splice", "concat",
|
|
423
|
+
"join", "split", "replace", "match", "search", "trim", "toLowerCase",
|
|
424
|
+
"toUpperCase", "includes", "indexOf", "startsWith", "endsWith",
|
|
425
|
+
"sort", "reverse", "keys", "values", "entries", "has", "get", "set",
|
|
426
|
+
"toString", "toISOString", "getTime", "getFullYear", "getMonth", "getDate",
|
|
427
|
+
"getHours", "getMinutes", "getSeconds", "floor", "ceil", "round",
|
|
428
|
+
"charAt", "charCodeAt", "substring", "substr", "padStart", "padEnd",
|
|
429
|
+
"getJsDocs", "getTags", "getTagName", "getCommentText", "getText",
|
|
430
|
+
"getName", "getType", "getExpression", "getArguments", "getParameters",
|
|
431
|
+
"getSourceFiles", "getFunctions", "getVariableDeclarations",
|
|
432
|
+
"getInitializer", "getReturnType", "isIdentifier", "isArrowFunction",
|
|
433
|
+
"isCallExpression", "isFunctionDeclaration", "isUnionTypeNode",
|
|
434
|
+
"isTypeReference", "isPropertyAccessExpression", "getTypeNodes",
|
|
435
|
+
"getTypeArguments", "getTypeName", "skip", "traversal",
|
|
436
|
+
"addSourceFilesAtPaths", "getFilePath",
|
|
437
|
+
"then", "catch", "resolve", "reject", // Promise
|
|
438
|
+
]);
|
|
439
|
+
// Minimal fallback registry (Node.js built-ins that ts-morph can't resolve)
|
|
440
|
+
const knownExternals = {
|
|
441
|
+
// fs
|
|
442
|
+
"readFileSync": {
|
|
443
|
+
params: [{ name: "path", type: "string" }, { name: "encoding", type: "string" }],
|
|
444
|
+
returnType: "string", description: "ๅๆญฅ่ฏปๅๆไปถๅ
ๅฎน",
|
|
445
|
+
},
|
|
446
|
+
"writeFileSync": {
|
|
447
|
+
params: [{ name: "path", type: "string" }, { name: "data", type: "string" }],
|
|
448
|
+
returnType: "void", description: "ๅๆญฅๅๅ
ฅๆไปถ",
|
|
449
|
+
},
|
|
450
|
+
"existsSync": {
|
|
451
|
+
params: [{ name: "path", type: "string" }],
|
|
452
|
+
returnType: "boolean", description: "ๆฃๆฅๆไปถๆฏๅฆๅญๅจ",
|
|
453
|
+
},
|
|
454
|
+
"readdirSync": {
|
|
455
|
+
params: [{ name: "path", type: "string" }],
|
|
456
|
+
returnType: "string[]", description: "่ฏปๅ็ฎๅฝๅ
ๅฎน",
|
|
457
|
+
},
|
|
458
|
+
"mkdirSync": {
|
|
459
|
+
params: [{ name: "path", type: "string" }, { name: "options", type: "object" }],
|
|
460
|
+
returnType: "void", description: "ๅๅปบ็ฎๅฝ",
|
|
461
|
+
},
|
|
462
|
+
"unlinkSync": {
|
|
463
|
+
params: [{ name: "path", type: "string" }],
|
|
464
|
+
returnType: "void", description: "ๅ ้คๆไปถ",
|
|
465
|
+
},
|
|
466
|
+
"statSync": {
|
|
467
|
+
params: [{ name: "path", type: "string" }],
|
|
468
|
+
returnType: "object", description: "่ทๅๆไปถ็ถๆ",
|
|
469
|
+
},
|
|
470
|
+
// path
|
|
471
|
+
"resolve": {
|
|
472
|
+
params: [{ name: "segments", type: "string[]" }],
|
|
473
|
+
returnType: "string", description: "่งฃๆ่ทฏๅพๆฎตไธบ็ปๅฏน่ทฏๅพ",
|
|
474
|
+
},
|
|
475
|
+
"relative": {
|
|
476
|
+
params: [{ name: "from", type: "string" }, { name: "to", type: "string" }],
|
|
477
|
+
returnType: "string", description: "่ฎก็ฎ็ธๅฏน่ทฏๅพ",
|
|
478
|
+
},
|
|
479
|
+
"dirname": {
|
|
480
|
+
params: [{ name: "path", type: "string" }],
|
|
481
|
+
returnType: "string", description: "่ทๅ็ฎๅฝๅ",
|
|
482
|
+
},
|
|
483
|
+
"basename": {
|
|
484
|
+
params: [{ name: "path", type: "string" }],
|
|
485
|
+
returnType: "string", description: "่ทๅๆไปถๅ",
|
|
486
|
+
},
|
|
487
|
+
"extname": {
|
|
488
|
+
params: [{ name: "path", type: "string" }],
|
|
489
|
+
returnType: "string", description: "่ทๅๆไปถๆฉๅฑๅ",
|
|
490
|
+
},
|
|
491
|
+
// JSON
|
|
492
|
+
"parse": {
|
|
493
|
+
params: [{ name: "text", type: "string" }],
|
|
494
|
+
returnType: "any", description: "่งฃๆ JSON ๅญ็ฌฆไธฒ",
|
|
495
|
+
},
|
|
496
|
+
"stringify": {
|
|
497
|
+
params: [{ name: "value", type: "any" }, { name: "replacer", type: "any" }],
|
|
498
|
+
returnType: "string", description: "ๅบๅๅไธบ JSON ๅญ็ฌฆไธฒ",
|
|
499
|
+
},
|
|
500
|
+
// console
|
|
501
|
+
"log": {
|
|
502
|
+
params: [{ name: "message", type: "any" }],
|
|
503
|
+
returnType: "void", description: "่พๅบๆฅๅฟ",
|
|
504
|
+
},
|
|
505
|
+
"error": {
|
|
506
|
+
params: [{ name: "message", type: "any" }],
|
|
507
|
+
returnType: "void", description: "่พๅบ้่ฏฏๆฅๅฟ",
|
|
508
|
+
},
|
|
509
|
+
"warn": {
|
|
510
|
+
params: [{ name: "message", type: "any" }],
|
|
511
|
+
returnType: "void", description: "่พๅบ่ญฆๅๆฅๅฟ",
|
|
512
|
+
},
|
|
513
|
+
// Buffer
|
|
514
|
+
"from": {
|
|
515
|
+
params: [{ name: "data", type: "string" }, { name: "encoding", type: "string" }],
|
|
516
|
+
returnType: "Buffer", description: "ไปๅญ็ฌฆไธฒๅๅปบ Buffer",
|
|
517
|
+
},
|
|
518
|
+
// process
|
|
519
|
+
"exit": {
|
|
520
|
+
params: [{ name: "code", type: "number" }],
|
|
521
|
+
returnType: "void", description: "้ๅบ่ฟ็จ",
|
|
522
|
+
},
|
|
523
|
+
"cwd": {
|
|
524
|
+
params: [],
|
|
525
|
+
returnType: "string", description: "่ทๅๅฝๅๅทฅไฝ็ฎๅฝ",
|
|
526
|
+
},
|
|
527
|
+
// child_process
|
|
528
|
+
"execSync": {
|
|
529
|
+
params: [{ name: "command", type: "string" }],
|
|
530
|
+
returnType: "Buffer", description: "ๅๆญฅๆง่กๅฝไปค",
|
|
531
|
+
},
|
|
532
|
+
// crypto
|
|
533
|
+
"createHash": {
|
|
534
|
+
params: [{ name: "algorithm", type: "string" }],
|
|
535
|
+
returnType: "Hash", description: "ๅๅปบๅๅธๅฏน่ฑก",
|
|
536
|
+
},
|
|
537
|
+
"digest": {
|
|
538
|
+
params: [{ name: "encoding", type: "string" }],
|
|
539
|
+
returnType: "string", description: "่พๅบๅๅธๆ่ฆ",
|
|
540
|
+
},
|
|
541
|
+
"update": {
|
|
542
|
+
params: [{ name: "data", type: "string" }],
|
|
543
|
+
returnType: "Hash", description: "ๆดๆฐๅๅธๆฐๆฎ",
|
|
544
|
+
},
|
|
545
|
+
// Date
|
|
546
|
+
"now": {
|
|
547
|
+
params: [],
|
|
548
|
+
returnType: "number", description: "ๅฝๅๆถ้ดๆณ๏ผๆฏซ็ง๏ผ",
|
|
549
|
+
},
|
|
550
|
+
// Object
|
|
551
|
+
"entries": {
|
|
552
|
+
params: [{ name: "obj", type: "object" }],
|
|
553
|
+
returnType: "Array<[string, any]>", description: "่ฟๅๅฏน่ฑก็้ฎๅผๅฏนๆฐ็ป",
|
|
554
|
+
},
|
|
555
|
+
// setTimeout / setInterval
|
|
556
|
+
"setTimeout": {
|
|
557
|
+
params: [{ name: "callback", type: "function" }, { name: "ms", type: "number" }],
|
|
558
|
+
returnType: "number", description: "ๅปถ่ฟๆง่กๅ่ฐ",
|
|
559
|
+
},
|
|
560
|
+
// Math
|
|
561
|
+
"random": {
|
|
562
|
+
params: [],
|
|
563
|
+
returnType: "number", description: "็ๆ 0-1 ้ๆบๆฐ",
|
|
564
|
+
},
|
|
565
|
+
"abs": {
|
|
566
|
+
params: [{ name: "x", type: "number" }],
|
|
567
|
+
returnType: "number", description: "็ปๅฏนๅผ",
|
|
568
|
+
},
|
|
569
|
+
"max": {
|
|
570
|
+
params: [{ name: "values", type: "number[]" }],
|
|
571
|
+
returnType: "number", description: "ๆๅคงๅผ",
|
|
572
|
+
},
|
|
573
|
+
"min": {
|
|
574
|
+
params: [{ name: "values", type: "number[]" }],
|
|
575
|
+
returnType: "number", description: "ๆๅฐๅผ",
|
|
576
|
+
},
|
|
577
|
+
};
|
|
578
|
+
let externalCount = 0;
|
|
579
|
+
let dynamicCount = 0;
|
|
580
|
+
let fallbackCount = 0;
|
|
581
|
+
for (const callName of allCalls) {
|
|
582
|
+
if (declaredNames.has(callName))
|
|
583
|
+
continue;
|
|
584
|
+
if (ignoredBuiltins.has(callName))
|
|
585
|
+
continue;
|
|
586
|
+
if (callName.startsWith("is") && callName[2] === callName[2]?.toUpperCase())
|
|
587
|
+
continue;
|
|
588
|
+
// Try namespace resolution for unresolved calls
|
|
589
|
+
if (!externalFuncs.has(callName) && !knownExternals[callName]) {
|
|
590
|
+
// Check if this function is called as X.method (property access)
|
|
591
|
+
// The callName is already the method name from extractDirectCalls
|
|
592
|
+
// Try each known namespace to find the function
|
|
593
|
+
for (const [nsPrefix] of namespaceImports) {
|
|
594
|
+
if (tryResolveFromNamespace(callName, nsPrefix))
|
|
595
|
+
break;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
// Priority: dynamic resolution > knownExternals fallback > any
|
|
599
|
+
const dynamic = externalFuncs.get(callName);
|
|
600
|
+
const fallback = knownExternals[callName];
|
|
601
|
+
if (dynamic) {
|
|
602
|
+
funcs.push({
|
|
603
|
+
name: callName,
|
|
604
|
+
params: dynamic.params,
|
|
605
|
+
returnType: dynamic.returnType,
|
|
606
|
+
returnTypeDetail: dynamic.returnType,
|
|
607
|
+
file: "(external)",
|
|
608
|
+
calls: [],
|
|
609
|
+
external: true,
|
|
610
|
+
description: dynamic.description,
|
|
611
|
+
});
|
|
612
|
+
dynamicCount++;
|
|
613
|
+
}
|
|
614
|
+
else if (fallback) {
|
|
615
|
+
funcs.push({
|
|
616
|
+
name: callName,
|
|
617
|
+
params: fallback.params,
|
|
618
|
+
returnType: fallback.returnType,
|
|
619
|
+
returnTypeDetail: fallback.returnType,
|
|
620
|
+
file: "(external)",
|
|
621
|
+
calls: [],
|
|
622
|
+
external: true,
|
|
623
|
+
description: fallback.description,
|
|
624
|
+
});
|
|
625
|
+
fallbackCount++;
|
|
626
|
+
}
|
|
627
|
+
else {
|
|
628
|
+
funcs.push({
|
|
629
|
+
name: callName,
|
|
630
|
+
params: [],
|
|
631
|
+
returnType: "any",
|
|
632
|
+
returnTypeDetail: "any",
|
|
633
|
+
file: "(external)",
|
|
634
|
+
calls: [],
|
|
635
|
+
external: true,
|
|
636
|
+
});
|
|
637
|
+
externalCount++;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
const totalExternal = dynamicCount + fallbackCount + externalCount;
|
|
641
|
+
console.error(`๐ฆ ๅค้จๅฝๆฐ: ${totalExternal} (ๅจๆ=${dynamicCount} ๅ้=${fallbackCount} ๆช็ญพๅ=${externalCount}, from ${allCalls.size} total calls)`);
|
|
642
|
+
// Post-process: auto-derive tags from file names
|
|
643
|
+
for (const f of funcs) {
|
|
644
|
+
if (!f.tags || f.tags.length === 0) {
|
|
645
|
+
const derived = deriveTagsFromFile(f.file);
|
|
646
|
+
if (derived.length > 0)
|
|
647
|
+
f.tags = derived;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
// Build typeโmodule map for emitter
|
|
651
|
+
const _typeMap = {};
|
|
652
|
+
for (const _sf of project.getSourceFiles()) {
|
|
653
|
+
if (_sf.getFilePath().includes("node_modules"))
|
|
654
|
+
continue;
|
|
655
|
+
const _rp = path.relative(absRoot, _sf.getFilePath());
|
|
656
|
+
for (const _iface of _sf.getInterfaces()) {
|
|
657
|
+
const _n = _iface.getName();
|
|
658
|
+
if (_n && _iface.isExported())
|
|
659
|
+
_typeMap[_n] = _rp;
|
|
660
|
+
}
|
|
661
|
+
for (const _ta of _sf.getTypeAliases()) {
|
|
662
|
+
const _n = _ta.getName();
|
|
663
|
+
if (_n && _ta.isExported())
|
|
664
|
+
_typeMap[_n] = _rp;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return { functions: funcs, typeMap: _typeMap };
|
|
668
|
+
}
|
|
669
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
670
|
+
// Dynamic external signature extraction helpers
|
|
671
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
672
|
+
/** Extract function signature from a resolved ts-morph source file. */
|
|
673
|
+
function extractSignatureFromFile(name, sf) {
|
|
674
|
+
for (const exp of sf.getExportedDeclarations().entries()) {
|
|
675
|
+
if (exp[0] !== name)
|
|
676
|
+
continue;
|
|
677
|
+
for (const decl of exp[1]) {
|
|
678
|
+
if (ts_morph_1.Node.isFunctionDeclaration(decl)) {
|
|
679
|
+
const params = decl.getParameters().map(p => ({
|
|
680
|
+
name: p.getName(),
|
|
681
|
+
type: p.getTypeNode()?.getText() || "any",
|
|
682
|
+
}));
|
|
683
|
+
return {
|
|
684
|
+
params,
|
|
685
|
+
returnType: decl.getReturnTypeNode()?.getText() || "any",
|
|
686
|
+
description: `auto-resolved from ${sf.getFilePath()}`,
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
if (ts_morph_1.Node.isVariableDeclaration(decl)) {
|
|
690
|
+
const init = decl.getInitializer();
|
|
691
|
+
if (init && ts_morph_1.Node.isArrowFunction(init)) {
|
|
692
|
+
const params = init.getParameters().map(p => ({
|
|
693
|
+
name: p.getName(),
|
|
694
|
+
type: p.getTypeNode()?.getText() || "any",
|
|
695
|
+
}));
|
|
696
|
+
return {
|
|
697
|
+
params,
|
|
698
|
+
returnType: init.getReturnTypeNode()?.getText() || "any",
|
|
699
|
+
description: `auto-resolved arrow from ${sf.getFilePath()}`,
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
return null;
|
|
706
|
+
}
|
|
707
|
+
/** Extract function signature from a .d.ts file using ts-morph. */
|
|
708
|
+
function extractSignatureFromDts(name, dtsPath, project) {
|
|
709
|
+
try {
|
|
710
|
+
const sf = project.addSourceFileAtPathIfExists(dtsPath);
|
|
711
|
+
if (!sf)
|
|
712
|
+
return null;
|
|
713
|
+
return extractSignatureFromFile(name, sf);
|
|
714
|
+
}
|
|
715
|
+
catch {
|
|
716
|
+
return null;
|
|
130
717
|
}
|
|
131
|
-
return funcs;
|
|
132
718
|
}
|
|
133
719
|
// ่ฅ็ดๆฅ่ฟ่ก
|
|
134
720
|
if (require.main === module) {
|
|
@@ -137,7 +723,8 @@ if (require.main === module) {
|
|
|
137
723
|
console.error("็จๆณ: ts-node extract-ir.ts <้กน็ฎๆ น>");
|
|
138
724
|
process.exit(1);
|
|
139
725
|
}
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
726
|
+
const result = extractIRWithTypes(root);
|
|
727
|
+
const data = { typeMap: result.typeMap, functions: result.functions };
|
|
728
|
+
fs.writeFileSync("ir.json", JSON.stringify(data, null, 2));
|
|
729
|
+
console.log(`โ
IR ๆๅๅฎๆ: ${result.functions.length} ไธชๅฝๆฐ, ${Object.keys(result.typeMap).length} ไธช็ฑปๅ -> ir.json`);
|
|
143
730
|
}
|