progmune-runtime 2.0.4 โ†’ 2.1.0

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