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.
Files changed (110) 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/README.md +108 -54
  7. package/demo-project/auth.ts +55 -0
  8. package/demo-project/tsconfig.json +8 -0
  9. package/dist/ab-stats.js +11 -0
  10. package/dist/acl-breakdown.js +13 -0
  11. package/dist/action-runtime.js +1 -0
  12. package/dist/all-sessions.js +11 -0
  13. package/dist/antibody-stats.js +11 -0
  14. package/dist/audit.js +222 -0
  15. package/dist/benchmark-count.js +7 -0
  16. package/dist/benchmark-full.js +17 -0
  17. package/dist/benchmark-pass-rate.js +54 -0
  18. package/dist/benchmark-report.js +67 -0
  19. package/dist/benchmark-save.js +62 -0
  20. package/dist/benchmark-status.js +15 -0
  21. package/dist/branch-ledger.js +393 -0
  22. package/dist/branch-tree-count.js +14 -0
  23. package/dist/check.js +506 -0
  24. package/dist/common-fixpath.js +12 -0
  25. package/dist/constraint-types.js +12 -0
  26. package/dist/deterministic-replay.js +283 -0
  27. package/dist/emitter.js +143 -12
  28. package/dist/exec-metrics.js +11 -0
  29. package/dist/execute.js +251 -0
  30. package/dist/extract-ir.js +592 -5
  31. package/dist/failure-collector.js +163 -0
  32. package/dist/failure-corpus.js +507 -35
  33. package/dist/failure-report.js +11 -0
  34. package/dist/failures.js +11 -0
  35. package/dist/fast-path-hits.js +13 -0
  36. package/dist/feedback.js +10 -3
  37. package/dist/file-lock.js +82 -0
  38. package/dist/find-session.js +10 -0
  39. package/dist/fingerprint-list.js +15 -0
  40. package/dist/gen-history-log.js +13 -0
  41. package/dist/generate.js +2 -1
  42. package/dist/generate_500.js +4 -2
  43. package/dist/genome.js +11 -0
  44. package/dist/heatmap-data.js +11 -0
  45. package/dist/heatmap.js +11 -0
  46. package/dist/immune-reporter.js +34 -22
  47. package/dist/ir-utils.js +18 -0
  48. package/dist/learned.js +11 -0
  49. package/dist/ledger-registry.js +252 -0
  50. package/dist/llm.js +46 -2
  51. package/dist/load-benchmarks.js +47 -0
  52. package/dist/main.js +2 -1
  53. package/dist/mcp-server.mjs +445 -43
  54. package/dist/memory-layer.js +54 -13
  55. package/dist/metrics.js +11 -0
  56. package/dist/obs-web.js +561 -0
  57. package/dist/p0_ssg_demo.js +255 -40
  58. package/dist/planner.js +996 -80
  59. package/dist/protocol-registry.js +112 -0
  60. package/dist/recent-session.js +12 -0
  61. package/dist/repair-proposal.js +363 -0
  62. package/dist/runtime-invariants.js +170 -0
  63. package/dist/runtime-types.js +117 -0
  64. package/dist/runtime.js +1 -0
  65. package/dist/search-planner.js +39 -9
  66. package/dist/semantic-snapshot.js +157 -0
  67. package/dist/semantic-trace.js +1497 -0
  68. package/dist/semantic-validator.js +3 -2
  69. package/dist/semantic_guard_test.js +2 -1
  70. package/dist/session-utils.js +19 -0
  71. package/dist/sessions.js +11 -0
  72. package/dist/ssg-validator.js +666 -20
  73. package/dist/svl-distribution.js +11 -0
  74. package/dist/terminal-status.js +11 -0
  75. package/dist/test_failure_corpus.js +3 -1
  76. package/dist/token-savings.js +11 -0
  77. package/dist/total-repairs.js +12 -0
  78. package/dist/unresolved-count.js +12 -0
  79. package/dist/utils.js +2 -0
  80. package/dist/valid-fingerprints.js +13 -0
  81. package/dist/validator.js +118 -60
  82. package/dist/verify-fps.js +11 -0
  83. package/dist/verify-ledgers.js +11 -0
  84. package/docs/whitepaper-style.css +77 -0
  85. package/docs/whitepaper-v2.1.md +609 -0
  86. package/docs/whitepaper-v2.2.md +1064 -0
  87. package/docs/whitepaper-v2.2.pdf +0 -0
  88. package/fly.toml +1 -1
  89. package/package.json +13 -4
  90. package/protocols.json +131 -11
  91. package/public/dashboard.html +119 -0
  92. package/server/hub.js +84 -12
  93. package/test/replay-golden/sess_1780063202050_mgeld.json +9 -0
  94. package/test/replay-golden/sess_1780064032560_gocld.json +354 -0
  95. package/test/replay-golden/sess_1780064413331_s2709.json +606 -0
  96. package/test/replay-golden/sess_1780064792710_y3avo.json +614 -0
  97. package/test/replay-golden.ts +84 -0
  98. package/test_benchmark.js +165 -0
  99. package/test_comprehensive.mjs +638 -0
  100. package/test_concurrency.js +129 -0
  101. package/test_ir_robustness.js +85 -0
  102. package/test_semantic_contracts.js +269 -0
  103. package/test_ssg_stress.js +156 -0
  104. package/test_svl3.js +58 -0
  105. package/tsconfig.json +1 -1
  106. package/.env.example +0 -3
  107. package/.progmune_memory/episodic.json +0 -186
  108. package/.progmune_memory/fingerprints.json +0 -7
  109. package/.progmune_memory/opt_in.json +0 -4
  110. package/immune_hub_data/2026-05-14.json +0 -50
@@ -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 fns = extractIR(root);
141
- fs.writeFileSync("ir.json", JSON.stringify(fns, null, 2));
142
- console.log(`โœ… IR ๆๅ–ๅฎŒๆˆ: ${fns.length} ไธชๅ‡ฝๆ•ฐ -> ir.json`);
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
  }