@nudojs/service 1.0.1 → 2.0.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.
package/dist/index.js CHANGED
@@ -1,17 +1,21 @@
1
1
  import {
2
+ DEFAULT_ANALYSIS_MODE,
3
+ analysisConfig,
2
4
  builtinProtoMember,
3
5
  builtinProtoMemberNames,
6
+ clearPathEnvCaches,
4
7
  describeAbsMember,
8
+ diskCacheRoot,
5
9
  findProjectConfig,
6
10
  interfaceConfig,
7
11
  loadEnvs,
8
12
  matchesEmitAllowlist,
9
13
  preloadPathEnvs
10
- } from "./chunk-2VHITMTM.js";
14
+ } from "./chunk-ADFCZP72.js";
11
15
 
12
16
  // src/analyzer.ts
13
- import { readFileSync as readFileSync5, existsSync as existsSync4, statSync as statSync4, realpathSync } from "fs";
14
- import { resolve as resolve5, dirname as dirname6 } from "path";
17
+ import { readFileSync as readFileSync5, existsSync as existsSync5, statSync as statSync5, realpathSync } from "fs";
18
+ import { resolve as resolve6, dirname as dirname7 } from "path";
15
19
  import traverse from "@babel/traverse";
16
20
  import {
17
21
  createEnvironment as createEnvironment2,
@@ -34,6 +38,9 @@ import {
34
38
  strLit,
35
39
  boolLit,
36
40
  abs as makeAbsVal,
41
+ formalParamsFromNodes,
42
+ formalParamDisplayNames,
43
+ runWithEvalMissingSlot,
37
44
  stableAnalyzeKeySource as stableAnalyzeKeySource2,
38
45
  fnFingerprints
39
46
  } from "@nudojs/core";
@@ -383,9 +390,70 @@ function defaultLoadModule(spec, fromFile) {
383
390
  }
384
391
  }
385
392
 
393
+ // src/env-path-deps.ts
394
+ import { existsSync as existsSync2, statSync as statSync2 } from "fs";
395
+ import { dirname as dirname2, resolve as resolve2, join as join2 } from "path";
396
+ import { extractAllLoadSpecs } from "@nudojs/core";
397
+ var envDependents = /* @__PURE__ */ new Map();
398
+ function norm(p) {
399
+ return p.replace(/\\/g, "/");
400
+ }
401
+ function resolveLoadSpecPath(spec, fromFile) {
402
+ if (!spec.startsWith(".") && !spec.startsWith("/")) return void 0;
403
+ try {
404
+ const base = dirname2(resolve2(fromFile));
405
+ const p = resolve2(base, spec);
406
+ for (const cand of [
407
+ p,
408
+ `${p}.js`,
409
+ `${p}.mjs`,
410
+ `${p}.ts`,
411
+ join2(p, "index.js"),
412
+ join2(p, "index.mjs"),
413
+ join2(p, "index.ts")
414
+ ]) {
415
+ if (existsSync2(cand) && !statSync2(cand).isDirectory()) return cand;
416
+ }
417
+ return void 0;
418
+ } catch {
419
+ return void 0;
420
+ }
421
+ }
422
+ function noteEnvPathDeps(sourcePath, source) {
423
+ const src = norm(sourcePath);
424
+ for (const set of envDependents.values()) set.delete(src);
425
+ let specs = [];
426
+ try {
427
+ specs = extractAllLoadSpecs(source);
428
+ } catch {
429
+ return;
430
+ }
431
+ for (const spec of specs) {
432
+ if (!spec.startsWith(".") && !spec.startsWith("/")) continue;
433
+ const abs = resolveLoadSpecPath(spec, sourcePath);
434
+ if (!abs) continue;
435
+ const key = norm(abs);
436
+ if (!envDependents.has(key)) envDependents.set(key, /* @__PURE__ */ new Set());
437
+ envDependents.get(key).add(src);
438
+ }
439
+ }
440
+ function envPathDependents(envPath) {
441
+ return [...envDependents.get(norm(envPath)) ?? []];
442
+ }
443
+ function clearEnvPathDeps() {
444
+ envDependents.clear();
445
+ }
446
+ function isEnvTemplatePath(path) {
447
+ const lower = norm(path).toLowerCase();
448
+ return lower.endsWith(".env.js") || lower.endsWith(".env.mjs") || lower.endsWith(".env.ts") || lower.endsWith(".env.cjs") || lower.endsWith(".env.cts") || lower.endsWith(".env.mts") || /\/mock-[^/]+\.(js|mjs|ts)$/.test(lower) || /\.mock\.(js|mjs|ts)$/.test(lower);
449
+ }
450
+
451
+ // src/analyzer.ts
452
+ import { loadModuleDepsFingerprint as loadModuleDepsFingerprint2, hashSource as hashSource2 } from "@nudojs/core";
453
+
386
454
  // src/abs-modules-graph.ts
387
- import { readFileSync as readFileSync4, statSync as statSync3 } from "fs";
388
- import { dirname as dirname5, resolve as resolve4 } from "path";
455
+ import { readFileSync as readFileSync4, statSync as statSync4 } from "fs";
456
+ import { dirname as dirname6, resolve as resolve5 } from "path";
389
457
  import { parse as parse3 } from "@nudojs/parser";
390
458
  import {
391
459
  evalProgramAbs,
@@ -398,19 +466,19 @@ import {
398
466
  confJoin as confJoin2,
399
467
  absFunction as absFunction2
400
468
  } from "@nudojs/core";
401
- import { dirname as dirname4 } from "path";
469
+ import { dirname as dirname5 } from "path";
402
470
 
403
471
  // src/harvest-auto.ts
404
472
  import { parse as parse2 } from "@nudojs/parser";
405
473
 
406
474
  // src/static-imports.ts
407
475
  import { parse } from "@nudojs/parser";
408
- import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
409
- import { resolve as resolve2, dirname as dirname2, join as join2 } from "path";
476
+ import { readFileSync as readFileSync2, existsSync as existsSync3 } from "fs";
477
+ import { resolve as resolve3, dirname as dirname3, join as join3 } from "path";
410
478
  import { generalizeFromAst } from "@nudojs/core";
411
479
  function resolveRelative(fromFile, spec) {
412
480
  if (!spec.startsWith(".") && !spec.startsWith("/")) return void 0;
413
- const base = resolve2(dirname2(fromFile), spec);
481
+ const base = resolve3(dirname3(fromFile), spec);
414
482
  for (const cand of [
415
483
  base,
416
484
  base + ".js",
@@ -419,12 +487,12 @@ function resolveRelative(fromFile, spec) {
419
487
  base + ".ts",
420
488
  base + ".mts",
421
489
  base + ".cts",
422
- join2(base, "index.js"),
423
- join2(base, "index.cjs"),
424
- join2(base, "index.mjs"),
425
- join2(base, "index.ts")
490
+ join3(base, "index.js"),
491
+ join3(base, "index.cjs"),
492
+ join3(base, "index.mjs"),
493
+ join3(base, "index.ts")
426
494
  ]) {
427
- if (existsSync2(cand)) return cand;
495
+ if (existsSync3(cand)) return cand;
428
496
  }
429
497
  return void 0;
430
498
  }
@@ -543,14 +611,14 @@ function collectCjsExports(stmt, named, setDefault) {
543
611
  function collectStaticImports(entryFile, maxDepth = 8) {
544
612
  const graph = /* @__PURE__ */ new Map();
545
613
  const queue = [
546
- { file: resolve2(entryFile), depth: 0 }
614
+ { file: resolve3(entryFile), depth: 0 }
547
615
  ];
548
616
  const seen = /* @__PURE__ */ new Set();
549
617
  while (queue.length > 0) {
550
618
  const { file, depth } = queue.shift();
551
619
  if (seen.has(file) || depth > maxDepth) continue;
552
620
  seen.add(file);
553
- if (!existsSync2(file)) continue;
621
+ if (!existsSync3(file)) continue;
554
622
  const source = readFileSync2(file, "utf8");
555
623
  const mod = analyzeExportsFromSource(file, source);
556
624
  graph.set(file, mod);
@@ -564,19 +632,19 @@ function collectStaticImports(entryFile, maxDepth = 8) {
564
632
  }
565
633
 
566
634
  // src/harvest-package.ts
567
- import { existsSync as existsSync3, readdirSync, statSync as statSync2, readFileSync as readFileSync3 } from "fs";
568
- import { join as join3, resolve as resolve3, dirname as dirname3, basename } from "path";
635
+ import { existsSync as existsSync4, readdirSync, statSync as statSync3, readFileSync as readFileSync3 } from "fs";
636
+ import { join as join4, resolve as resolve4, dirname as dirname4, basename } from "path";
569
637
  import { harvestDts } from "@nudojs/harvester";
570
638
  import { formatShape } from "@nudojs/core";
571
639
  function resolvePackageRoot(pkg, fromDir = process.cwd()) {
572
- let dir = resolve3(fromDir);
640
+ let dir = resolve4(fromDir);
573
641
  const bare = pkg.replace(/^@/, "").replace(/\//g, "__");
574
642
  for (let i = 0; i < 8; i++) {
575
- const typesPath = join3(dir, "node_modules", "@types", bare);
576
- if (existsSync3(typesPath)) return typesPath;
577
- const pkgPath = join3(dir, "node_modules", pkg);
578
- if (existsSync3(pkgPath)) return pkgPath;
579
- const parent = resolve3(dir, "..");
643
+ const typesPath = join4(dir, "node_modules", "@types", bare);
644
+ if (existsSync4(typesPath)) return typesPath;
645
+ const pkgPath = join4(dir, "node_modules", pkg);
646
+ if (existsSync4(pkgPath)) return pkgPath;
647
+ const parent = resolve4(dir, "..");
580
648
  if (parent === dir) break;
581
649
  dir = parent;
582
650
  }
@@ -606,10 +674,10 @@ function collectDtsFiles(root, maxFiles = 8) {
606
674
  for (const name of entries) {
607
675
  if (out.length >= maxFiles * 3) return;
608
676
  if (SKIP_DIRS.has(name)) continue;
609
- const p = join3(dir, name);
677
+ const p = join4(dir, name);
610
678
  let st;
611
679
  try {
612
- st = statSync2(p);
680
+ st = statSync3(p);
613
681
  } catch {
614
682
  continue;
615
683
  }
@@ -625,15 +693,15 @@ function collectDtsFiles(root, maxFiles = 8) {
625
693
  return out.slice(0, maxFiles);
626
694
  }
627
695
  function entryDtsFromPackageJson(root) {
628
- const pj = join3(root, "package.json");
629
- if (!existsSync3(pj)) return void 0;
696
+ const pj = join4(root, "package.json");
697
+ if (!existsSync4(pj)) return void 0;
630
698
  try {
631
699
  const raw = JSON.parse(readFileSync3(pj, "utf8"));
632
700
  const entry = raw.types ?? raw.typings;
633
701
  if (typeof entry === "string" && entry.endsWith(".d.ts")) {
634
- const p = resolve3(root, entry);
635
- if (existsSync3(p)) {
636
- const st = statSync2(p);
702
+ const p = resolve4(root, entry);
703
+ if (existsSync4(p)) {
704
+ const st = statSync3(p);
637
705
  if (st.size <= MAX_DTS_BYTES) return p;
638
706
  }
639
707
  }
@@ -649,7 +717,7 @@ function collectDtsFromEntry(entry, maxFiles = 200) {
649
717
  const RELATIVE_FROM_REGEX = /\bfrom\s+["'](\.[^"']+)["']/g;
650
718
  while (queue.length > 0 && files.length < maxFiles) {
651
719
  const current = queue.shift();
652
- if (seen.has(current) || !existsSync3(current)) continue;
720
+ if (seen.has(current) || !existsSync4(current)) continue;
653
721
  seen.add(current);
654
722
  if (!current.endsWith(".d.ts")) continue;
655
723
  files.push(current);
@@ -659,14 +727,14 @@ function collectDtsFromEntry(entry, maxFiles = 200) {
659
727
  } catch {
660
728
  continue;
661
729
  }
662
- const dir = dirname3(current);
730
+ const dir = dirname4(current);
663
731
  for (const match of text.matchAll(REFERENCE_PATH_REGEX)) {
664
- queue.push(resolve3(dir, match[1]));
732
+ queue.push(resolve4(dir, match[1]));
665
733
  }
666
734
  for (const match of text.matchAll(RELATIVE_FROM_REGEX)) {
667
- const base = resolve3(dir, match[1]);
668
- for (const candidate of [`${base}.d.ts`, join3(base, "index.d.ts")]) {
669
- if (existsSync3(candidate)) {
735
+ const base = resolve4(dir, match[1]);
736
+ for (const candidate of [`${base}.d.ts`, join4(base, "index.d.ts")]) {
737
+ if (existsSync4(candidate)) {
670
738
  queue.push(candidate);
671
739
  break;
672
740
  }
@@ -678,11 +746,11 @@ function collectDtsFromEntry(entry, maxFiles = 200) {
678
746
  function harvestPackage(pkg, fromDir, maxFiles = 8) {
679
747
  const root = resolvePackageRoot(pkg, fromDir);
680
748
  if (!root) return { error: `package not found: ${pkg}` };
681
- const entry = entryDtsFromPackageJson(root) ?? join3(root, "index.d.ts");
682
- let dtsFiles = existsSync3(entry) ? collectDtsFromEntry(entry, maxFiles) : [];
749
+ const entry = entryDtsFromPackageJson(root) ?? join4(root, "index.d.ts");
750
+ let dtsFiles = existsSync4(entry) ? collectDtsFromEntry(entry, maxFiles) : [];
683
751
  if (dtsFiles.length === 0) {
684
752
  const collected = collectDtsFiles(root, maxFiles);
685
- dtsFiles = entry && existsSync3(entry) && !collected.includes(entry) ? [entry, ...collected.filter((f) => f !== entry)].slice(0, maxFiles) : collected;
753
+ dtsFiles = entry && existsSync4(entry) && !collected.includes(entry) ? [entry, ...collected.filter((f) => f !== entry)].slice(0, maxFiles) : collected;
686
754
  }
687
755
  if (dtsFiles.length === 0) {
688
756
  return { error: `no .d.ts under ${root}` };
@@ -840,7 +908,7 @@ function bareSpecToAbsModules(spec, fromFile) {
840
908
  const parts = spec.split("/");
841
909
  const pkg = spec.startsWith("@") ? parts.length >= 2 ? `${parts[0]}/${parts[1]}` : void 0 : parts[0];
842
910
  if (!pkg) return void 0;
843
- const table = harvestToAbsModules(pkg, dirname4(fromFile));
911
+ const table = harvestToAbsModules(pkg, dirname5(fromFile));
844
912
  return table[spec] ?? table[pkg];
845
913
  }
846
914
 
@@ -848,8 +916,8 @@ function bareSpecToAbsModules(spec, fromFile) {
848
916
  function defaultAbsLoadModule(spec, fromFile) {
849
917
  if (!spec.startsWith(".") && !spec.startsWith("/")) return void 0;
850
918
  try {
851
- const p = resolve4(dirname5(resolve4(fromFile)), spec);
852
- for (const cand of [p, `${p}.js`, `${p}.mjs`, `${p}.ts`, resolve4(p, "index.js")]) {
919
+ const p = resolve5(dirname6(resolve5(fromFile)), spec);
920
+ for (const cand of [p, `${p}.js`, `${p}.mjs`, `${p}.ts`, resolve5(p, "index.js")]) {
853
921
  try {
854
922
  return readFileSync4(cand, "utf-8");
855
923
  } catch {
@@ -861,8 +929,8 @@ function defaultAbsLoadModule(spec, fromFile) {
861
929
  }
862
930
  }
863
931
  function relCandidates(spec, fromFile) {
864
- const p = resolve4(dirname5(resolve4(fromFile)), spec);
865
- return [p, `${p}.js`, `${p}.mjs`, `${p}.ts`, resolve4(p, "index.js")];
932
+ const p = resolve5(dirname6(resolve5(fromFile)), spec);
933
+ return [p, `${p}.js`, `${p}.mjs`, `${p}.ts`, resolve5(p, "index.js")];
866
934
  }
867
935
  function resolveRel(spec, fromFile) {
868
936
  if (!spec.startsWith(".") && !spec.startsWith("/")) return null;
@@ -966,7 +1034,7 @@ function evalAbsModuleGraph(entrySource, entryFile, opts = {}) {
966
1034
  const shared = absModuleCache.get(absPath);
967
1035
  if (shared) {
968
1036
  try {
969
- const st = statSync3(absPath);
1037
+ const st = statSync4(absPath);
970
1038
  if (st.mtimeMs === shared.mtimeMs && st.size === shared.size) {
971
1039
  for (const iss of shared.issues) pushIssue(iss.kind, iss.label, iss.reason);
972
1040
  cache.set(absPath, shared.exports);
@@ -1031,7 +1099,7 @@ function evalAbsModuleGraph(entrySource, entryFile, opts = {}) {
1031
1099
  cache.set(absPath, exports);
1032
1100
  let fingerprint;
1033
1101
  try {
1034
- const st = statSync3(absPath);
1102
+ const st = statSync4(absPath);
1035
1103
  fingerprint = { mtimeMs: st.mtimeMs, size: st.size };
1036
1104
  } catch {
1037
1105
  }
@@ -1102,7 +1170,8 @@ import {
1102
1170
  stableAnalyzeKeySource,
1103
1171
  formatAbs as formatAbs3,
1104
1172
  hashSource,
1105
- getFnImpl as getFnImpl2
1173
+ getFnImpl as getFnImpl2,
1174
+ loadModuleDepsFingerprint
1106
1175
  } from "@nudojs/core";
1107
1176
  import { parse as parse4, extractInlineDirectives } from "@nudojs/parser";
1108
1177
 
@@ -1304,6 +1373,15 @@ function isBPathCapable(source, envNames = []) {
1304
1373
  }
1305
1374
  var bRunByFile = /* @__PURE__ */ new Map();
1306
1375
  var MAX_B_RUN_CACHE = 32;
1376
+ function bPathDepKey(source, filePath) {
1377
+ try {
1378
+ const fp = loadModuleDepsFingerprint(source, defaultLoadModule, filePath);
1379
+ if (fp.truncated) return null;
1380
+ return hashSource(fp.fp);
1381
+ } catch {
1382
+ return null;
1383
+ }
1384
+ }
1307
1385
  function clearBPathCache() {
1308
1386
  bRunByFile.clear();
1309
1387
  clearAnalysisFileCache();
@@ -1316,12 +1394,12 @@ function evictBPathCacheForFiles(files) {
1316
1394
  }
1317
1395
  return n;
1318
1396
  }
1319
- function bPathCacheSet(filePath, stableSource, mode, envKey, mockKey, value) {
1397
+ function bPathCacheSet(filePath, stableSource, mode, envKey, mockKey, depKey, value) {
1320
1398
  if (bRunByFile.size >= MAX_B_RUN_CACHE && !bRunByFile.has(filePath)) {
1321
1399
  const oldest = bRunByFile.keys().next().value;
1322
1400
  if (oldest !== void 0) bRunByFile.delete(oldest);
1323
1401
  }
1324
- bRunByFile.set(filePath, { stableSource, mode, envKey, mockKey, value });
1402
+ bRunByFile.set(filePath, { stableSource, mode, envKey, mockKey, depKey, value });
1325
1403
  }
1326
1404
  function tryRunBPath(source, filePath, opts = {}) {
1327
1405
  if (!isBPathCapable(source, opts.envNames ?? [])) return void 0;
@@ -1329,11 +1407,15 @@ function tryRunBPath(source, filePath, opts = {}) {
1329
1407
  const envKey = (opts.envNames ?? []).join(",");
1330
1408
  const mockKey = mockSeedFingerprint(opts.mocks);
1331
1409
  const stable = stableAnalyzeKeySource(source);
1332
- const cached = bRunByFile.get(filePath);
1333
- if (cached && cached.stableSource === stable && cached.mode === mode && cached.envKey === envKey && cached.mockKey === mockKey) {
1334
- bRunByFile.delete(filePath);
1335
- bRunByFile.set(filePath, cached);
1336
- return cached.value ?? void 0;
1410
+ const depKey = bPathDepKey(source, filePath);
1411
+ const canCache = depKey !== null;
1412
+ if (canCache) {
1413
+ const cached = bRunByFile.get(filePath);
1414
+ if (cached && cached.stableSource === stable && cached.mode === mode && cached.envKey === envKey && cached.mockKey === mockKey && cached.depKey === depKey) {
1415
+ bRunByFile.delete(filePath);
1416
+ bRunByFile.set(filePath, cached);
1417
+ return cached.value ?? void 0;
1418
+ }
1337
1419
  }
1338
1420
  let out = null;
1339
1421
  try {
@@ -1378,7 +1460,9 @@ function tryRunBPath(source, filePath, opts = {}) {
1378
1460
  } catch {
1379
1461
  out = null;
1380
1462
  }
1381
- bPathCacheSet(filePath, stable, mode, envKey, mockKey, out);
1463
+ if (canCache && depKey !== null) {
1464
+ bPathCacheSet(filePath, stable, mode, envKey, mockKey, depKey, out);
1465
+ }
1382
1466
  return out ?? void 0;
1383
1467
  }
1384
1468
  function tryBPathCallFull(source, filePath, fnName, args, opts = {}) {
@@ -1520,6 +1604,9 @@ function collectDeclared(file) {
1520
1604
  case "ImportNamespaceSpecifier":
1521
1605
  addId(o.local);
1522
1606
  break;
1607
+ case "CatchClause":
1608
+ addId(o.param);
1609
+ break;
1523
1610
  default:
1524
1611
  break;
1525
1612
  }
@@ -1621,10 +1708,10 @@ function collectBPathDiagnostics(source, extraKnown) {
1621
1708
  // src/analyzer.ts
1622
1709
  import { setAbsTruncationCollector as setAbsTruncationCollector2 } from "@nudojs/core";
1623
1710
  function resolveImportPath(specifier, fromDir) {
1624
- const basePath = resolve5(fromDir, specifier);
1711
+ const basePath = resolve6(fromDir, specifier);
1625
1712
  for (const ext of ["", ".js", ".ts", ".mjs"]) {
1626
1713
  const candidate = basePath + ext;
1627
- if (existsSync4(candidate)) return candidate;
1714
+ if (existsSync5(candidate)) return candidate;
1628
1715
  }
1629
1716
  return null;
1630
1717
  }
@@ -1644,7 +1731,7 @@ function buildModuleGraph(files, cache) {
1644
1731
  if (cache) {
1645
1732
  let stat = null;
1646
1733
  try {
1647
- stat = statSync4(file);
1734
+ stat = statSync5(file);
1648
1735
  } catch {
1649
1736
  }
1650
1737
  const cached = stat ? cache.get(file) : void 0;
@@ -1673,7 +1760,7 @@ function extractImportEdges(file) {
1673
1760
  if (stmt.type !== "ImportDeclaration") continue;
1674
1761
  const specifier = stmt.source.value;
1675
1762
  if (!specifier.startsWith(".") && !specifier.startsWith("/")) continue;
1676
- const resolved = resolveImportPath(specifier, dirname6(file));
1763
+ const resolved = resolveImportPath(specifier, dirname7(file));
1677
1764
  if (resolved) edges.push(resolved);
1678
1765
  }
1679
1766
  return edges;
@@ -1748,23 +1835,13 @@ function locFromNode(node) {
1748
1835
  }
1749
1836
  function extractParamNames(node) {
1750
1837
  const fn = node.type === "ExportDefaultDeclaration" ? node.declaration : node;
1751
- if (fn.type === "FunctionDeclaration" || fn.type === "FunctionExpression" || fn.type === "ArrowFunctionExpression") {
1752
- return fn.params.map((p) => {
1753
- if (p.type === "Identifier") return p.name;
1754
- if (p.type === "AssignmentPattern" && p.left.type === "Identifier") return p.left.name;
1755
- if (p.type === "RestElement" && p.argument.type === "Identifier") return `...${p.argument.name}`;
1756
- return "_";
1757
- });
1838
+ if (fn.type === "FunctionDeclaration" || fn.type === "FunctionExpression" || fn.type === "ArrowFunctionExpression" || fn.type === "ClassMethod" || fn.type === "ObjectMethod" || fn.type === "TSDeclareMethod") {
1839
+ return formalParamDisplayNames(formalParamsFromNodes(fn.params));
1758
1840
  }
1759
1841
  if (fn.type === "VariableDeclaration") {
1760
1842
  const decl = fn.declarations[0];
1761
1843
  if (decl.init?.type === "FunctionExpression" || decl.init?.type === "ArrowFunctionExpression") {
1762
- return decl.init.params.map((p) => {
1763
- if (p.type === "Identifier") return p.name;
1764
- if (p.type === "AssignmentPattern" && p.left.type === "Identifier") return p.left.name;
1765
- if (p.type === "RestElement" && p.argument.type === "Identifier") return `...${p.argument.name}`;
1766
- return "_";
1767
- });
1844
+ return formalParamDisplayNames(formalParamsFromNodes(decl.init.params));
1768
1845
  }
1769
1846
  }
1770
1847
  return [];
@@ -1828,12 +1905,103 @@ function assignmentChainName(expr) {
1828
1905
  function collectTopLevelFunctions(ast) {
1829
1906
  const results = [];
1830
1907
  if (ast.type !== "File") return results;
1831
- for (const stmt of ast.program.body) {
1908
+ const body = ast.program.body;
1909
+ const classDecls = /* @__PURE__ */ new Map();
1910
+ const exportedClassNames = /* @__PURE__ */ new Set();
1911
+ const collectClassMethods = (cname, decl, stmt) => {
1912
+ const members = decl?.body?.body ?? [];
1913
+ for (const m of members) {
1914
+ const mem = m;
1915
+ const isMethod = mem.type === "MethodDefinition" || mem.type === "ClassMethod" || mem.type === "TSDeclareMethod";
1916
+ if (!isMethod) continue;
1917
+ if (mem.kind && mem.kind !== "method") continue;
1918
+ if (mem.static) continue;
1919
+ const keyName = mem.key?.type === "Identifier" ? mem.key.name : void 0;
1920
+ if (!keyName) continue;
1921
+ const methodNode = mem.type === "MethodDefinition" ? mem.value : mem;
1922
+ if (!methodNode) continue;
1923
+ results.push({
1924
+ name: `${cname}.${keyName}`,
1925
+ node: methodNode,
1926
+ stmt,
1927
+ noDeclaration: true
1928
+ });
1929
+ }
1930
+ };
1931
+ for (const stmt of body) {
1932
+ if (stmt.type === "ClassDeclaration" && stmt.id?.name) {
1933
+ const n = stmt.id.name;
1934
+ classDecls.set(n, { node: stmt, stmt });
1935
+ continue;
1936
+ }
1937
+ if (stmt.type === "ExportNamedDeclaration") {
1938
+ const d = stmt.declaration;
1939
+ if (d?.type === "ClassDeclaration" && d.id?.name) {
1940
+ const n = d.id.name;
1941
+ classDecls.set(n, { node: d, stmt });
1942
+ exportedClassNames.add(n);
1943
+ continue;
1944
+ }
1945
+ if (!d && stmt.specifiers) {
1946
+ for (const spec of stmt.specifiers) {
1947
+ if (spec?.type !== "ExportSpecifier") continue;
1948
+ const local = spec.local?.name;
1949
+ const exported = spec.exported?.name ?? spec.exported?.value;
1950
+ if (!local) continue;
1951
+ if (classDecls.has(local) || body.some(
1952
+ (s) => s.type === "ClassDeclaration" && s.id?.name === local
1953
+ )) {
1954
+ exportedClassNames.add(local);
1955
+ }
1956
+ if (exported === "default") exportedClassNames.add(local);
1957
+ }
1958
+ continue;
1959
+ }
1960
+ }
1961
+ if (stmt.type === "ExportDefaultDeclaration") {
1962
+ const d = stmt.declaration;
1963
+ if (d?.type === "ClassDeclaration" && d.id?.name) {
1964
+ const n = d.id.name;
1965
+ classDecls.set(n, { node: d, stmt });
1966
+ exportedClassNames.add(n);
1967
+ } else if (d?.type === "Identifier" && typeof d.name === "string") {
1968
+ exportedClassNames.add(d.name);
1969
+ }
1970
+ continue;
1971
+ }
1972
+ }
1973
+ for (const stmt of body) {
1832
1974
  const decl = resolveFunctionNode(stmt);
1833
1975
  if (decl.type === "FunctionDeclaration" && decl.id) {
1834
1976
  results.push({ name: decl.id.name, node: decl, stmt, noDeclaration: false });
1835
1977
  continue;
1836
1978
  }
1979
+ if (decl.type === "ClassDeclaration" && decl.id?.name) {
1980
+ continue;
1981
+ }
1982
+ if (stmt.type === "ExportNamedDeclaration") {
1983
+ const d = resolveFunctionNode(stmt);
1984
+ if (d.type === "FunctionDeclaration" && d.id) {
1985
+ results.push({ name: d.id.name, node: d, stmt, noDeclaration: false });
1986
+ }
1987
+ if (d.type === "ClassDeclaration" && d.id?.name) {
1988
+ }
1989
+ continue;
1990
+ }
1991
+ if (stmt.type === "ExportDefaultDeclaration") {
1992
+ const d = stmt.declaration;
1993
+ if (d?.type === "FunctionDeclaration" && d.id) {
1994
+ results.push({ name: d.id.name, node: d, stmt, noDeclaration: false });
1995
+ } else if (d && (isFnExprValue(d) || d.type === "FunctionDeclaration" && !d.id)) {
1996
+ results.push({
1997
+ name: "default",
1998
+ node: d,
1999
+ stmt,
2000
+ noDeclaration: true
2001
+ });
2002
+ }
2003
+ continue;
2004
+ }
1837
2005
  if (stmt.type === "VariableDeclaration") {
1838
2006
  for (const declarator of stmt.declarations) {
1839
2007
  if (declarator.id?.type === "Identifier" && isFnExprValue(declarator.init)) {
@@ -1851,6 +2019,11 @@ function collectTopLevelFunctions(ast) {
1851
2019
  }
1852
2020
  }
1853
2021
  }
2022
+ for (const cname of exportedClassNames) {
2023
+ const hit = classDecls.get(cname);
2024
+ if (!hit) continue;
2025
+ collectClassMethods(cname, hit.node, hit.stmt);
2026
+ }
1854
2027
  return results;
1855
2028
  }
1856
2029
  function findSingleModuleExportsFunction(ast) {
@@ -1877,7 +2050,7 @@ function findSingleModuleExportsFunction(ast) {
1877
2050
  }
1878
2051
  return found;
1879
2052
  }
1880
- var MAX_PRECISE_CALLSITE_CASES = 3;
2053
+ var DEFAULT_CALLSITE_BUDGET = 3;
1881
2054
  var COLLAPSE_LITERAL_THRESHOLD = 4;
1882
2055
  function dedupeCallRecords(records) {
1883
2056
  const seen = /* @__PURE__ */ new Set();
@@ -1895,7 +2068,7 @@ function locFromCallLoc(loc) {
1895
2068
  const p = loc ?? { line: 0, column: 0 };
1896
2069
  return { start: { line: p.line, column: p.column }, end: { line: p.line, column: p.column } };
1897
2070
  }
1898
- function synthesizeExternalFunctions(records, currentFile) {
2071
+ function synthesizeExternalFunctions(records, currentFile, callSiteBudget = DEFAULT_CALLSITE_BUDGET) {
1899
2072
  const groups = /* @__PURE__ */ new Map();
1900
2073
  for (const rec of records) {
1901
2074
  if (!rec.targetModule || !rec.targetExport) continue;
@@ -1919,7 +2092,7 @@ function synthesizeExternalFunctions(records, currentFile) {
1919
2092
  cases: [],
1920
2093
  fromModule: module
1921
2094
  };
1922
- const precise = deduped.slice(0, MAX_PRECISE_CALLSITE_CASES);
2095
+ const precise = deduped.slice(0, callSiteBudget);
1923
2096
  for (const rec of precise) {
1924
2097
  analysis.cases.push({
1925
2098
  name: `call@L${rec.callLoc?.line ?? 0}`,
@@ -1929,7 +2102,7 @@ function synthesizeExternalFunctions(records, currentFile) {
1929
2102
  source: "callsite"
1930
2103
  });
1931
2104
  }
1932
- const remaining = deduped.slice(MAX_PRECISE_CALLSITE_CASES);
2105
+ const remaining = deduped.slice(callSiteBudget);
1933
2106
  if (remaining.length > 0) {
1934
2107
  const symArgsAbs = Array.from(
1935
2108
  { length: arity },
@@ -1970,16 +2143,17 @@ function collectEnvNames(filePath, source, includeProject) {
1970
2143
  const fileDirectives = extractFileDirectives(ast);
1971
2144
  const fileEnvNames = fileDirectives.filter((d) => d.kind === "env").flatMap((d) => d.envs);
1972
2145
  if (!includeProject) return fileEnvNames;
1973
- const projectConfig = findProjectConfig(dirname6(filePath));
2146
+ const projectConfig = findProjectConfig(dirname7(filePath));
1974
2147
  const projectEnvNames = projectConfig?.config.env ?? [];
1975
2148
  return [.../* @__PURE__ */ new Set([...projectEnvNames, ...fileEnvNames])];
1976
2149
  }
1977
- async function analyzeFileAsync(filePath, source, activeCases, externalCallRecords) {
2150
+ async function analyzeFileAsync(filePath, source, activeCases, externalCallRecords, loadModule) {
1978
2151
  const envNames = collectEnvNames(filePath, source, true);
2152
+ noteEnvPathDeps(filePath, source);
1979
2153
  if (envNames.length > 0) {
1980
- await preloadPathEnvs(envNames, dirname6(filePath));
2154
+ await preloadPathEnvs(envNames, dirname7(filePath));
1981
2155
  }
1982
- return analyzeFile(filePath, source, activeCases, externalCallRecords);
2156
+ return analyzeFile(filePath, source, activeCases, externalCallRecords, loadModule);
1983
2157
  }
1984
2158
  function collectCallRecords(filePath, source) {
1985
2159
  if (/\brequire\s*\(/.test(source) && filePath) {
@@ -2050,7 +2224,7 @@ function collectCallRecords(filePath, source) {
2050
2224
  var TEST_CALLBACK_NAMES = /* @__PURE__ */ new Set(["it", "test", "describe"]);
2051
2225
  var externalRecordIds = /* @__PURE__ */ new WeakMap();
2052
2226
  var nextExternalRecordId = 1;
2053
- function analysisFileCacheKey(filePath, source, activeCases, externalCallRecords) {
2227
+ function analysisFileCacheKey(filePath, source, activeCases, externalCallRecords, analysisCfg, loadModule, projectEnvNames, autoBind) {
2054
2228
  let cases = "-";
2055
2229
  if (activeCases && activeCases.size > 0) {
2056
2230
  cases = [...activeCases.entries()].sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0).map(([k, v]) => `${k}=${v}`).join(",");
@@ -2064,11 +2238,31 @@ function analysisFileCacheKey(filePath, source, activeCases, externalCallRecords
2064
2238
  }
2065
2239
  ext = `n${externalCallRecords.length}#id${id}`;
2066
2240
  }
2241
+ const cfg = analysisCfg ? `m=${analysisCfg.mode}|e=${analysisCfg.evalMissingSlot}|b=${analysisCfg.callSiteBudget}|d=${analysisCfg.diagnostics}` : "-";
2242
+ const lm = loadModule !== void 0 && loadModule !== defaultLoadModule ? "lm1" : "lm0";
2243
+ const envSeg = projectEnvNames && projectEnvNames.length > 0 ? projectEnvNames.join(",") : "-";
2244
+ const abSeg = autoBind === false ? "ab0" : autoBind === true ? "ab1" : "ab?";
2245
+ const effectiveLoader = loadModule ?? defaultLoadModule;
2246
+ let depSeg = "-";
2247
+ let noCache = false;
2248
+ try {
2249
+ const fp = loadModuleDepsFingerprint2(source, effectiveLoader, filePath);
2250
+ if (fp.truncated) {
2251
+ noCache = true;
2252
+ depSeg = `trunc:${fp.paths.length}`;
2253
+ } else {
2254
+ depSeg = hashSource2(fp.fp);
2255
+ }
2256
+ } catch {
2257
+ noCache = true;
2258
+ depSeg = "fperr";
2259
+ }
2067
2260
  return {
2068
2261
  filePath,
2069
2262
  // 尾部无 @nudo 注释/空行不进键:comment-only 编辑命中 AnalysisResult
2070
2263
  source: stableAnalyzeKeySource2(source),
2071
- auxKey: `${cases}\0${ext}`
2264
+ auxKey: `${cases}\0${ext}\0${cfg}\0${lm}\0${envSeg}\0${abSeg}\0${depSeg}`,
2265
+ noCache
2072
2266
  };
2073
2267
  }
2074
2268
  function cloneAnalysisResult(r) {
@@ -2086,12 +2280,20 @@ function cloneFunctionAnalysis(a) {
2086
2280
  return {
2087
2281
  ...a,
2088
2282
  paramNames: [...a.paramNames],
2283
+ ...a.formals ? { formals: a.formals.map((f) => ({ ...f })) } : {},
2089
2284
  cases: a.cases.map((c) => ({
2090
2285
  ...c,
2091
2286
  argAbs: [...c.argAbs],
2092
2287
  ...c.intension ? { intension: { ...c.intension } } : {}
2093
2288
  })),
2094
- loc: { start: { ...a.loc.start }, end: { ...a.loc.end } }
2289
+ loc: { start: { ...a.loc.start }, end: { ...a.loc.end } },
2290
+ ...a.hof ? {
2291
+ hof: {
2292
+ ...a.hof.fnRels ? { fnRels: a.hof.fnRels.map((r) => ({ ...r })) } : {},
2293
+ ...a.hof.entryShapes ? { entryShapes: a.hof.entryShapes.map((s) => ({ ...s })) } : {},
2294
+ ...a.hof.symbolic ? { symbolic: a.hof.symbolic } : {}
2295
+ }
2296
+ } : {}
2095
2297
  };
2096
2298
  }
2097
2299
  function shiftSourceLoc(loc, lineDelta) {
@@ -2115,17 +2317,42 @@ function shiftCallRecordLines(r, lineDelta) {
2115
2317
  callLoc: { line: r.callLoc.line + lineDelta, column: r.callLoc.column }
2116
2318
  };
2117
2319
  }
2118
- function analyzeFile(filePath, source, activeCases, externalCallRecords) {
2119
- const k = analysisFileCacheKey(filePath, source, activeCases, externalCallRecords);
2120
- const hit = analysisCacheGet(k.filePath, k.source, k.auxKey);
2121
- if (hit !== void 0) {
2122
- return cloneAnalysisResult(hit);
2320
+ function analyzeFile(filePath, source, activeCases, externalCallRecords, loadModule) {
2321
+ const projectConfig = findProjectConfig(dirname7(filePath));
2322
+ const cfg = analysisConfig(projectConfig?.config);
2323
+ const projectEnvNames = projectConfig?.config.env ?? [];
2324
+ const autoBind = interfaceConfig(projectConfig?.config).autoBind;
2325
+ const k = analysisFileCacheKey(
2326
+ filePath,
2327
+ source,
2328
+ activeCases,
2329
+ externalCallRecords,
2330
+ cfg,
2331
+ loadModule,
2332
+ projectEnvNames,
2333
+ autoBind !== false
2334
+ );
2335
+ if (!k.noCache) {
2336
+ const hit = analysisCacheGet(k.filePath, k.source, k.auxKey);
2337
+ if (hit !== void 0) {
2338
+ return cloneAnalysisResult(hit);
2339
+ }
2340
+ }
2341
+ const result = analyzeFileUncached(filePath, source, activeCases, externalCallRecords, loadModule);
2342
+ if (!k.noCache) {
2343
+ analysisCacheSet(k.filePath, k.source, k.auxKey, result);
2123
2344
  }
2124
- const result = analyzeFileUncached(filePath, source, activeCases, externalCallRecords);
2125
- analysisCacheSet(k.filePath, k.source, k.auxKey, result);
2126
2345
  return cloneAnalysisResult(result);
2127
2346
  }
2128
- function analyzeFileUncached(filePath, source, activeCases, externalCallRecords) {
2347
+ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords, loadModule) {
2348
+ const projectConfig = findProjectConfig(dirname7(filePath));
2349
+ const missingSlotOn = analysisConfig(projectConfig?.config).evalMissingSlot === "warning";
2350
+ return runWithEvalMissingSlot(
2351
+ missingSlotOn,
2352
+ () => analyzeFileUncachedInner(filePath, source, activeCases, externalCallRecords, loadModule)
2353
+ );
2354
+ }
2355
+ function analyzeFileUncachedInner(filePath, source, activeCases, externalCallRecords, loadModule) {
2129
2356
  const ast = parse5(source);
2130
2357
  const functions = extractDirectives(ast);
2131
2358
  const diagnostics = [];
@@ -2135,18 +2362,33 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
2135
2362
  const caseHints = [];
2136
2363
  const fileDirectives = extractFileDirectives(ast);
2137
2364
  const fileEnvNames = fileDirectives.filter((d) => d.kind === "env").flatMap((d) => d.envs);
2138
- const projectConfig = findProjectConfig(dirname6(filePath));
2365
+ const projectConfig = findProjectConfig(dirname7(filePath));
2139
2366
  const projectEnvNames = projectConfig?.config.env ?? [];
2140
2367
  const envNames = [.../* @__PURE__ */ new Set([...projectEnvNames, ...fileEnvNames])];
2368
+ const analysisCfg = analysisConfig(projectConfig?.config);
2369
+ const callSiteBudget = analysisCfg.callSiteBudget;
2141
2370
  const callRecords = [];
2142
2371
  const globalEnv = createEnvironment2();
2143
2372
  const bMemberDiagNames = /* @__PURE__ */ new Set();
2144
2373
  const bMemberDiagSeen = /* @__PURE__ */ new Set();
2145
2374
  const pushBMemberDiag = (d, fallbackLine) => {
2146
2375
  bMemberDiagNames.add(d.name);
2147
- const key = `${d.kind}:${d.name}:${d.receiver}:${d.line ?? fallbackLine}:${d.column ?? 0}`;
2376
+ const key = `${d.kind}:${d.name}:${d.receiver}:${d.line ?? fallbackLine}:${d.column ?? 0}:${d.code ?? ""}`;
2148
2377
  if (bMemberDiagSeen.has(key)) return;
2149
2378
  bMemberDiagSeen.add(key);
2379
+ if (d.code === "nudo:missing-slot") {
2380
+ diagnostics.push({
2381
+ range: {
2382
+ start: { line: d.line ?? fallbackLine, column: d.column ?? 0 },
2383
+ end: { line: d.line ?? fallbackLine, column: (d.column ?? 0) + d.name.length }
2384
+ },
2385
+ severity: "warning",
2386
+ message: `Field '${d.name}' is missing on the evaluated object shape`,
2387
+ code: "nudo:missing-slot",
2388
+ ...d.origin ? { origin: d.origin } : {}
2389
+ });
2390
+ return;
2391
+ }
2150
2392
  if (d.receiver === "unknown") {
2151
2393
  diagnostics.push({
2152
2394
  range: {
@@ -2204,7 +2446,8 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
2204
2446
  try {
2205
2447
  const g = evalAbsModuleGraph(source, filePath, {
2206
2448
  seedVars: seeds.seedVars,
2207
- seedFns: seeds.seedFns
2449
+ seedFns: seeds.seedFns,
2450
+ ...loadModule ? { loadModule } : {}
2208
2451
  });
2209
2452
  absGraphModules = { ...collectEnvModules(envNames), ...g.modules };
2210
2453
  pushBModuleIssues(g.issues);
@@ -2235,7 +2478,7 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
2235
2478
  if (selfContained || canAbsModules) {
2236
2479
  setAbsTruncationCollector2((label) => bTruncatedFns.add(label));
2237
2480
  try {
2238
- absCallRecords = collectAbsCallRecords(source, seeds, filePath, absGraphModules, envNames);
2481
+ absCallRecords = collectAbsCallRecords(source, seeds, filePath, absGraphModules, envNames, loadModule);
2239
2482
  } finally {
2240
2483
  setAbsTruncationCollector2(null);
2241
2484
  }
@@ -2257,7 +2500,10 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
2257
2500
  } catch {
2258
2501
  }
2259
2502
  }
2260
- if ((selfContained || canAbsModules) && absCallRecords.length > 0) {
2503
+ if (bHostedEval && bTopCallRecords.length > 0) {
2504
+ callRecords.length = 0;
2505
+ callRecords.push(...bTopCallRecords);
2506
+ } else if ((selfContained || canAbsModules) && absCallRecords.length > 0) {
2261
2507
  callRecords.length = 0;
2262
2508
  callRecords.push(...absCallRecords);
2263
2509
  } else if (bTopCallRecords.length > 0) {
@@ -2345,7 +2591,16 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
2345
2591
  const skipDirective = fn.directives.find((d) => d.kind === "skip");
2346
2592
  const fnLoc = locFromNode(fn.node);
2347
2593
  const paramNames = extractParamNames(fn.node);
2348
- const analysis = { name: fn.name, loc: fnLoc, paramNames, cases: [] };
2594
+ const formals = formalParamsFromNodes(
2595
+ fn.node.params ?? []
2596
+ );
2597
+ const analysis = {
2598
+ name: fn.name,
2599
+ loc: fnLoc,
2600
+ paramNames,
2601
+ ...formals.length > 0 ? { formals } : {},
2602
+ cases: []
2603
+ };
2349
2604
  if (skipDirective && skipDirective.kind === "skip") {
2350
2605
  analysis.skipped = true;
2351
2606
  if (skipDirective.returns) {
@@ -2357,14 +2612,31 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
2357
2612
  const caseDirectives = fn.directives.filter((d) => d.kind === "case");
2358
2613
  const activeCaseIdx = activeCases?.get(fn.name) ?? 0;
2359
2614
  const fp = fnFpMap?.get(fn.name);
2360
- const fnCacheKey = fp && caseDirectives.length > 0 ? [
2615
+ const analysisFnKey = `m=${analysisCfg.mode}|e=${analysisCfg.evalMissingSlot}|b=${analysisCfg.callSiteBudget}`;
2616
+ let fnDepSeg = "-";
2617
+ let fnDepFailClosed = false;
2618
+ try {
2619
+ const dfp = loadModuleDepsFingerprint2(source, loadModule ?? defaultLoadModule, filePath);
2620
+ if (dfp.truncated) {
2621
+ fnDepFailClosed = true;
2622
+ fnDepSeg = null;
2623
+ } else {
2624
+ fnDepSeg = hashSource2(dfp.fp);
2625
+ }
2626
+ } catch {
2627
+ fnDepFailClosed = true;
2628
+ fnDepSeg = null;
2629
+ }
2630
+ const fnCacheKey = !fnDepFailClosed && fp && caseDirectives.length > 0 ? [
2361
2631
  filePath,
2362
2632
  fp.own,
2363
2633
  fp.deps,
2364
2634
  String(activeCaseIdx),
2365
2635
  caseDirectiveKey(caseDirectives, formatAbs4),
2366
2636
  envKeyFn,
2367
- mockKeyFn
2637
+ mockKeyFn,
2638
+ analysisFnKey,
2639
+ fnDepSeg ?? "-"
2368
2640
  ].join("\0") : void 0;
2369
2641
  const dLen0 = diagnostics.length;
2370
2642
  const hLen0 = caseHints.length;
@@ -2538,6 +2810,7 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
2538
2810
  COLLAPSE_LITERAL_THRESHOLD
2539
2811
  );
2540
2812
  }
2813
+ attachHofSnapshot(analysis, source);
2541
2814
  if (fnCacheKey) {
2542
2815
  fnAnalysisCacheSet(fnCacheKey, {
2543
2816
  analysis: cloneFunctionAnalysis(analysis),
@@ -2571,7 +2844,7 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
2571
2844
  }
2572
2845
  return n;
2573
2846
  };
2574
- const currentModulePath = normalizeModulePath(resolve5(filePath));
2847
+ const currentModulePath = normalizeModulePath(resolve6(filePath));
2575
2848
  const singleExportFn = findSingleModuleExportsFunction(ast);
2576
2849
  const reportInjectedDomainExceeds = (name, node, fallbackLoc) => {
2577
2850
  if (!externalCallRecords || externalCallRecords.length === 0) return;
@@ -2586,10 +2859,10 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
2586
2859
  if (injected.length === 0) return;
2587
2860
  const nameLoc = fnNameLoc(node, fallbackLoc);
2588
2861
  const fnNode = resolveFunctionNode(node);
2589
- const autoBind = interfaceConfig(findProjectConfig(dirname6(filePath))?.config).autoBind;
2862
+ const autoBind = interfaceConfig(findProjectConfig(dirname7(filePath))?.config).autoBind;
2590
2863
  const domainIssues = checkInjectedDomainEvidence(name, source, injected, {
2591
2864
  paramNames: extractParamNames(fnNode),
2592
- loadModule: defaultLoadModule,
2865
+ loadModule: loadModule ?? defaultLoadModule,
2593
2866
  fromFile: filePath,
2594
2867
  loc: { line: nameLoc.start.line, column: nameLoc.start.column },
2595
2868
  ...autoBind === false ? { autoBind: false } : {}
@@ -2639,7 +2912,7 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
2639
2912
  return 0;
2640
2913
  };
2641
2914
  const ordered = records.map((r, i) => ({ r, i })).sort((a, b) => informativeness(a.r) - informativeness(b.r) || a.i - b.i).map(({ r }) => r);
2642
- const precise = ordered.slice(0, MAX_PRECISE_CALLSITE_CASES);
2915
+ const precise = ordered.slice(0, callSiteBudget);
2643
2916
  for (const rec of precise) {
2644
2917
  let absRaw;
2645
2918
  let absResult;
@@ -2666,7 +2939,7 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
2666
2939
  if (absRaw) attachAbsToIntension(caseResult2, absRaw, candidate.name);
2667
2940
  candidate.analysis.cases.push(caseResult2);
2668
2941
  }
2669
- const remaining = ordered.slice(MAX_PRECISE_CALLSITE_CASES).filter((rec) => !rec.argAbs.some((a) => a.shape.k === "unknown" && !a.term));
2942
+ const remaining = ordered.slice(callSiteBudget).filter((rec) => !rec.argAbs.some((a) => a.shape.k === "unknown" && !a.term));
2670
2943
  if (remaining.length > 0) {
2671
2944
  const fnNode2 = resolveFunctionNode(candidate.node);
2672
2945
  const paramCount = extractParamNames(fnNode2).length;
@@ -2707,7 +2980,8 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
2707
2980
  const symCase = {
2708
2981
  name: "call@symbolic",
2709
2982
  argAbs: widenedArgsAbs,
2710
- abs: symAbs ?? absUnknown2,
2983
+ // B4:超预算聚合必须 #widened(可解释降级)
2984
+ abs: symAbs ? { ...symAbs, conf: symAbs.conf === "exact" ? "widened" : symAbs.conf } : absUnknown2,
2711
2985
  throwsAbs: neverAbs,
2712
2986
  source: "callsite",
2713
2987
  aggregatedFrom: remaining.length
@@ -2771,11 +3045,16 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
2771
3045
  candidate.analysis.cases.push(caseResult);
2772
3046
  candidate.analysis.entryOnly = true;
2773
3047
  candidate.analysis.combinedAbs = entryAbs;
3048
+ attachHofSnapshot(candidate.analysis, source);
2774
3049
  }
2775
3050
  if (!bHostedEval) {
2776
3051
  buildNodeTypeMap(ast, globalEnv, nodeAbsMap);
2777
3052
  }
2778
- const externalFunctions = synthesizeExternalFunctions(callRecords, filePath);
3053
+ const externalFunctions = synthesizeExternalFunctions(
3054
+ callRecords,
3055
+ filePath,
3056
+ analysisConfig(projectConfig?.config).callSiteBudget
3057
+ );
2779
3058
  return {
2780
3059
  functions: functionResults,
2781
3060
  diagnostics,
@@ -2870,7 +3149,7 @@ function absModulesOk(source, envNames) {
2870
3149
  void envNames;
2871
3150
  return true;
2872
3151
  }
2873
- function collectAbsCallRecords(source, seeds, filePath, precomputedModules, envNames = []) {
3152
+ function collectAbsCallRecords(source, seeds, filePath, precomputedModules, envNames = [], loadModule) {
2874
3153
  const absCalls = [];
2875
3154
  let modules = precomputedModules;
2876
3155
  let importLocals = /* @__PURE__ */ new Map();
@@ -2879,7 +3158,8 @@ function collectAbsCallRecords(source, seeds, filePath, precomputedModules, envN
2879
3158
  try {
2880
3159
  const graph = evalAbsModuleGraph(source, filePath, {
2881
3160
  seedVars: seeds?.seedVars,
2882
- seedFns: seeds?.seedFns
3161
+ seedFns: seeds?.seedFns,
3162
+ ...loadModule ? { loadModule } : {}
2883
3163
  });
2884
3164
  modules = graph.modules;
2885
3165
  } catch {
@@ -2986,10 +3266,10 @@ function buildAbsImportLocalMap(source, fromFile) {
2986
3266
  return out;
2987
3267
  }
2988
3268
  function resolveImportAbs(spec, fromFile) {
2989
- const base = dirname6(resolve5(fromFile));
2990
- const p = resolve5(base, spec);
2991
- for (const cand of [p, `${p}.js`, `${p}.mjs`, `${p}.ts`, resolve5(p, "index.js")]) {
2992
- if (existsSync4(cand) && !statSync4(cand).isDirectory()) return cand;
3269
+ const base = dirname7(resolve6(fromFile));
3270
+ const p = resolve6(base, spec);
3271
+ for (const cand of [p, `${p}.js`, `${p}.mjs`, `${p}.ts`, resolve6(p, "index.js")]) {
3272
+ if (existsSync5(cand) && !statSync5(cand).isDirectory()) return cand;
2993
3273
  }
2994
3274
  return null;
2995
3275
  }
@@ -3063,6 +3343,22 @@ function tryAttachIntension(caseResult, source, fnName) {
3063
3343
  } catch {
3064
3344
  }
3065
3345
  }
3346
+ function attachHofSnapshot(analysis, source) {
3347
+ if (analysis.hof) return;
3348
+ try {
3349
+ const g = generalizeFromAst2(analysis.name, source);
3350
+ if (!g) return;
3351
+ const fnRels = g.fnRels ? [...g.fnRels.entries()].map(([param, rec]) => ({ param, abs: rec.abs })) : void 0;
3352
+ const entryShapes = g.entryShapes ? [...g.entryShapes.entries()].map(([param, rec]) => ({ param, abs: rec.abs })) : void 0;
3353
+ if (!fnRels?.length && !entryShapes?.length) return;
3354
+ analysis.hof = {
3355
+ ...fnRels?.length ? { fnRels } : {},
3356
+ ...entryShapes?.length ? { entryShapes } : {},
3357
+ symbolic: g.symbolic
3358
+ };
3359
+ } catch {
3360
+ }
3361
+ }
3066
3362
  function attachAbsToIntension(caseResult, absVal, label) {
3067
3363
  const prev = caseResult.intension ?? {};
3068
3364
  caseResult.abs = absVal;
@@ -3074,10 +3370,27 @@ function attachAbsToIntension(caseResult, absVal, label) {
3074
3370
  };
3075
3371
  }
3076
3372
  function safeAbsOrUnknown(a) {
3077
- if (!a || typeof a !== "object" || !("shape" in a) || !a.shape) {
3078
- return { shape: { k: "unknown" }, conf: "opaque" };
3373
+ if (a && typeof a === "object" && "shape" in a && "conf" in a) {
3374
+ return a;
3375
+ }
3376
+ if (typeof a === "function") {
3377
+ const fn = a;
3378
+ const n = Math.max(0, fn.length);
3379
+ const params = Array.from({ length: n }, (_, i) => `arg${i}`);
3380
+ return absFunction4(params, {
3381
+ body: { type: "BlockStatement", body: [], directives: [] },
3382
+ apply: (args) => {
3383
+ try {
3384
+ const r = fn(...args);
3385
+ if (r && typeof r === "object" && "shape" in r) return r;
3386
+ return absUnknown2;
3387
+ } catch {
3388
+ return absUnknown2;
3389
+ }
3390
+ }
3391
+ });
3079
3392
  }
3080
- return a;
3393
+ return absUnknown2;
3081
3394
  }
3082
3395
  function callRecordFromAbsCall(r, impMap) {
3083
3396
  const argAbs = r.args.map(safeAbsOrUnknown);
@@ -3099,7 +3412,7 @@ function callRecordFromAbsCall(r, impMap) {
3099
3412
  }
3100
3413
 
3101
3414
  // src/lsp-surface.ts
3102
- import { dirname as dirname7 } from "path";
3415
+ import { dirname as dirname8 } from "path";
3103
3416
  import traverse2 from "@babel/traverse";
3104
3417
  import {
3105
3418
  generalizeFromAst as generalizeFromAst3,
@@ -3109,7 +3422,8 @@ import {
3109
3422
  collectAbsNodeTypes,
3110
3423
  findAbsAtPosition,
3111
3424
  evalSource,
3112
- setAbsNodeCollector as setAbsNodeCollector2
3425
+ setAbsNodeCollector as setAbsNodeCollector2,
3426
+ interfaceTierOf
3113
3427
  } from "@nudojs/core";
3114
3428
  import { parse as parse6, extractDirectives as extractDirectives2, extractFileDirectives as extractFileDirectives2 } from "@nudojs/parser";
3115
3429
  function getCasesForFile(filePath, source) {
@@ -3123,14 +3437,14 @@ function getCasesForFile(filePath, source) {
3123
3437
  async function getTypeAtPositionAsync(filePath, source, line, column, activeCases) {
3124
3438
  const envNames = collectEnvNames(filePath, source, false);
3125
3439
  if (envNames.length > 0) {
3126
- await preloadPathEnvs(envNames, dirname7(filePath));
3440
+ await preloadPathEnvs(envNames, dirname8(filePath));
3127
3441
  }
3128
3442
  return getTypeAtPosition(filePath, source, line, column, activeCases);
3129
3443
  }
3130
3444
  async function getAbsAtPositionAsync(filePath, source, line, column, activeCases) {
3131
3445
  const envNames = collectEnvNames(filePath, source, false);
3132
3446
  if (envNames.length > 0) {
3133
- await preloadPathEnvs(envNames, dirname7(filePath));
3447
+ await preloadPathEnvs(envNames, dirname8(filePath));
3134
3448
  }
3135
3449
  return getAbsAtPosition(filePath, source, line, column, activeCases);
3136
3450
  }
@@ -3218,7 +3532,7 @@ function getAbsAtPosition(filePath, source, line, column, activeCases) {
3218
3532
  function getTypeAtPosition(filePath, source, line, column, activeCases) {
3219
3533
  return getAbsAtPosition(filePath, source, line, column, activeCases);
3220
3534
  }
3221
- function getHoverAtPosition(filePath, source, line, column, activeCases) {
3535
+ function getHoverAtPosition(filePath, source, line, column, activeCases, opts) {
3222
3536
  let file;
3223
3537
  try {
3224
3538
  file = parse6(source);
@@ -3227,6 +3541,7 @@ function getHoverAtPosition(filePath, source, line, column, activeCases) {
3227
3541
  }
3228
3542
  const envNames = collectEnvNames(filePath, source, false);
3229
3543
  const fnName = findFunctionNameAtPosition(source, line, column, file);
3544
+ const tier = fnName !== void 0 ? interfaceTierOf(source, fnName, filePath, opts ?? {}) : void 0;
3230
3545
  let gDisplay;
3231
3546
  let gAbs;
3232
3547
  let gMulti;
@@ -3241,18 +3556,26 @@ function getHoverAtPosition(filePath, source, line, column, activeCases) {
3241
3556
  } catch {
3242
3557
  }
3243
3558
  }
3559
+ const withTier = (info) => {
3560
+ if (!info || !tier) return info;
3561
+ return {
3562
+ ...info,
3563
+ interfaceSource: tier.source,
3564
+ ...tier.display !== void 0 ? { interfaceDisplay: tier.display } : {}
3565
+ };
3566
+ };
3244
3567
  const attachIntension = (info) => {
3245
- if (!gDisplay) return info;
3568
+ if (!gDisplay) return withTier(info);
3246
3569
  if (!info) {
3247
- return { typeText: gDisplay, intension: gDisplay, abs: gAbs, absMultiline: gMulti };
3570
+ return withTier({ typeText: gDisplay, intension: gDisplay, abs: gAbs, absMultiline: gMulti });
3248
3571
  }
3249
- return {
3572
+ return withTier({
3250
3573
  ...info,
3251
3574
  intension: gDisplay,
3252
3575
  // 外延侧已有更准 Abs 时保留;否则用 symbolic 兜底
3253
3576
  abs: info.abs ?? gAbs,
3254
3577
  absMultiline: info.absMultiline ?? gMulti
3255
- };
3578
+ });
3256
3579
  };
3257
3580
  const insideCaseFn = positionInsideCaseFunction(
3258
3581
  source,
@@ -3333,6 +3656,14 @@ function getHoverAtPosition(filePath, source, line, column, activeCases) {
3333
3656
  }
3334
3657
  } catch {
3335
3658
  }
3659
+ if (tier && gDisplay) {
3660
+ return withTier({
3661
+ typeText: gDisplay,
3662
+ intension: gDisplay,
3663
+ abs: gAbs,
3664
+ absMultiline: gMulti
3665
+ });
3666
+ }
3336
3667
  return null;
3337
3668
  }
3338
3669
  function findIdentNameAtPosition(source, line, column, fileAst) {
@@ -3657,10 +3988,111 @@ function serializeInferJson(result, file) {
3657
3988
  function isNudoTargetPath(path) {
3658
3989
  const lower = path.toLowerCase();
3659
3990
  if (lower.endsWith(".d.ts")) return false;
3660
- if (lower.endsWith(".nudo.js") || lower.endsWith(".nudo.ts")) return false;
3991
+ if (lower.endsWith(".nudo.js") || lower.endsWith(".nudo.mjs") || lower.endsWith(".nudo.ts") || lower.endsWith(".nudo.draft.js") || lower.endsWith(".nudo.draft.mjs") || lower.endsWith(".nudo.draft.ts")) {
3992
+ return false;
3993
+ }
3661
3994
  return lower.endsWith(".js") || lower.endsWith(".mjs") || lower.endsWith(".ts");
3662
3995
  }
3663
3996
 
3997
+ // src/watch-paths.ts
3998
+ var PROJECT_CONFIG_BASENAMES = /* @__PURE__ */ new Set([
3999
+ "package.json",
4000
+ "nudo.json",
4001
+ "nudo.config.js",
4002
+ "nudo.config.mjs",
4003
+ "nudo.config.ts",
4004
+ ".nudorc",
4005
+ ".nudorc.json"
4006
+ ]);
4007
+ function isSidecarPath(path) {
4008
+ const lower = path.toLowerCase().replace(/\\/g, "/");
4009
+ if (lower.includes(".nudo.draft.")) return false;
4010
+ return lower.endsWith(".nudo.js") || lower.endsWith(".nudo.mjs") || lower.endsWith(".nudo.ts");
4011
+ }
4012
+ function isProjectConfigPath(path) {
4013
+ const norm2 = path.replace(/\\/g, "/");
4014
+ if (/\/node_modules\//.test(`/${norm2}`)) return false;
4015
+ const base = norm2.split("/").pop() ?? path;
4016
+ return PROJECT_CONFIG_BASENAMES.has(base.toLowerCase());
4017
+ }
4018
+ function isWatchRelevantPath(path) {
4019
+ return isNudoTargetPath(path) || isSidecarPath(path) || isProjectConfigPath(path) || isEnvTemplatePath(path);
4020
+ }
4021
+ function ambientSourcesOfSidecar(sidecarPath) {
4022
+ const norm2 = sidecarPath.replace(/\\/g, "/");
4023
+ const m = norm2.match(/^(.*)\.nudo\.(js|ts)$/i);
4024
+ if (!m || norm2.toLowerCase().includes(".nudo.draft.")) return [];
4025
+ const base = m[1];
4026
+ const ext = m[2].toLowerCase();
4027
+ return ext === "js" ? [`${base}.js`, `${base}.mjs`] : [`${base}.ts`, `${base}.mts`];
4028
+ }
4029
+
4030
+ // src/analysis-scope.ts
4031
+ import { dirname as dirname9 } from "path";
4032
+ import { existsSync as existsSync6 } from "fs";
4033
+ import { sidecarPathOf } from "@nudojs/core";
4034
+ var NOISY_WARNING_CODES = /* @__PURE__ */ new Set([
4035
+ "nudo:unknown-recv",
4036
+ "nudo:builtin-unknown",
4037
+ "nudo:no-signature"
4038
+ ]);
4039
+ function filterDiagnosticsByLevel(diags, level) {
4040
+ if (level === "verbose") return diags;
4041
+ if (level === "off") return [];
4042
+ if (level === "errors") return diags.filter((d) => d.severity === "error");
4043
+ return diags.filter((d) => {
4044
+ if (d.severity === "error") return true;
4045
+ if (d.severity === "info") return false;
4046
+ if (d.severity === "warning" && d.code && NOISY_WARNING_CODES.has(d.code)) return false;
4047
+ return d.severity === "warning";
4048
+ });
4049
+ }
4050
+ function diagnosticsLevelForFile(filePath) {
4051
+ return analysisConfig(findProjectConfig(dirname9(filePath))?.config).diagnostics;
4052
+ }
4053
+ function hasNudoDirectives(source) {
4054
+ return /@nudo:(case|mock|pure|skip|sample|refine|interface|import|env|mock-module|as|replace)\b/.test(source);
4055
+ }
4056
+ function stripCommentsAndStrings(source) {
4057
+ return source.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/\/\/[^\n]*/g, " ").replace(/'(?:\\.|[^'\\])*'|"(?:\\.|[^"\\])*"|`(?:\\.|[^`\\])*`/g, '""');
4058
+ }
4059
+ function hasExport(source) {
4060
+ const s = stripCommentsAndStrings(source);
4061
+ return /(^|[\s;}])export\b/.test(s) || /\bmodule\.exports\b/.test(s) || /\bexports\s*[.[]/.test(s) || /Object\.assign\s*\(\s*(module\.)?exports\b/.test(s);
4062
+ }
4063
+ function shouldAnalyzeFile(filePath, source, config) {
4064
+ if (!isNudoTargetPath(filePath)) return false;
4065
+ const proj = findProjectConfig(dirname9(filePath));
4066
+ const cfg = config ?? analysisConfig(proj?.config);
4067
+ const projectDir = proj?.projectDir;
4068
+ if (cfg.exclude.length > 0 && projectDir) {
4069
+ if (matchesEmitAllowlist(filePath, projectDir, cfg.exclude)) return false;
4070
+ } else if (cfg.exclude.length > 0 && !projectDir) {
4071
+ const norm2 = filePath.replace(/\\/g, "/");
4072
+ if (cfg.exclude.some((p) => simpleExcludeHit(p, norm2))) return false;
4073
+ }
4074
+ if (cfg.include.length > 0 && projectDir) {
4075
+ if (!matchesEmitAllowlist(filePath, projectDir, cfg.include)) return false;
4076
+ }
4077
+ if (cfg.mode === "all") return true;
4078
+ const text = source;
4079
+ if (text !== void 0 && hasNudoDirectives(text)) return true;
4080
+ if (cfg.mode === "directives") return false;
4081
+ if (text !== void 0 && hasExport(text)) return true;
4082
+ try {
4083
+ if (existsSync6(sidecarPathOf(filePath))) return true;
4084
+ } catch {
4085
+ }
4086
+ return false;
4087
+ }
4088
+ function simpleExcludeHit(pattern, path) {
4089
+ const bare = pattern.replace(/^\*\*/, "").replace(/^\//, "").replace(/\/\*\*$/, "").replace(/^\*\//, "");
4090
+ if (!bare || bare.includes("*")) {
4091
+ return /\/(node_modules|dist|coverage)\//.test("/" + path + "/");
4092
+ }
4093
+ return path.includes(`/${bare}/`) || path.endsWith(`/${bare}`);
4094
+ }
4095
+
3664
4096
  // src/session-cache.ts
3665
4097
  import {
3666
4098
  resetGeneralizeMemo,
@@ -3674,10 +4106,12 @@ function evictAnalysisCachesForFiles(files) {
3674
4106
  evictAnalysisFileCacheForFiles(files);
3675
4107
  evictFnAnalysisCacheForFiles(files);
3676
4108
  evictAbsModuleCacheFiles(files);
4109
+ clearPathEnvCaches();
3677
4110
  }
3678
4111
  function clearAnalysisSessionCaches() {
3679
4112
  clearBPathCache();
3680
4113
  clearAbsModuleCache();
4114
+ clearPathEnvCaches();
3681
4115
  resetGeneralizeMemo();
3682
4116
  resetCheckSourceMemo();
3683
4117
  resetNudoModuleExecCache();
@@ -3687,12 +4121,183 @@ function resetAllAnalysisCaches() {
3687
4121
  resetParseSourceCache();
3688
4122
  }
3689
4123
 
4124
+ // src/analysis-session.ts
4125
+ function createDefaultSession() {
4126
+ return {
4127
+ evictForDependents(files) {
4128
+ evictAnalysisCachesForFiles(files);
4129
+ },
4130
+ clear() {
4131
+ clearAnalysisSessionCaches();
4132
+ },
4133
+ reset() {
4134
+ resetAllAnalysisCaches();
4135
+ },
4136
+ analyze(filePath, source, activeCases, externalCallRecords) {
4137
+ return analyzeFile(
4138
+ filePath,
4139
+ source,
4140
+ activeCases,
4141
+ externalCallRecords
4142
+ );
4143
+ }
4144
+ };
4145
+ }
4146
+ var defaultSession = void 0;
4147
+ function getAnalysisSession() {
4148
+ if (!defaultSession) defaultSession = createDefaultSession();
4149
+ return defaultSession;
4150
+ }
4151
+ function setAnalysisSession(session) {
4152
+ const prev = defaultSession;
4153
+ defaultSession = session;
4154
+ return prev;
4155
+ }
4156
+
4157
+ // src/disk-cache.ts
4158
+ import { createHash } from "crypto";
4159
+ import { createRequire } from "module";
4160
+ import { mkdirSync, readFileSync as readFileSync6, writeFileSync, existsSync as existsSync7, rmSync } from "fs";
4161
+ import { join as join5, dirname as dirname10, relative, sep, isAbsolute } from "path";
4162
+ function readServiceVersion() {
4163
+ try {
4164
+ const require2 = createRequire(import.meta.url);
4165
+ for (const p of ["../package.json", "./package.json", "../../package.json"]) {
4166
+ try {
4167
+ const pkg = require2(p);
4168
+ if (pkg?.name === "@nudojs/service" && pkg.version) return pkg.version;
4169
+ if (pkg?.version && p.includes("service")) return pkg.version;
4170
+ } catch {
4171
+ }
4172
+ }
4173
+ } catch {
4174
+ }
4175
+ return "0";
4176
+ }
4177
+ var ANALYSIS_ABI = `nudo-check-cache-v2+${readServiceVersion()}`;
4178
+ function sha256Hex(data) {
4179
+ return createHash("sha256").update(data).digest("hex");
4180
+ }
4181
+ function relativizePath(p, root) {
4182
+ const norm2 = p.split(sep).join("/");
4183
+ if (!root) return norm2;
4184
+ const r = relative(root, p).split(sep).join("/");
4185
+ if (!r.startsWith("..") && !isAbsolute(r)) return r;
4186
+ return `ext:${createHash("sha256").update(norm2).digest("hex").slice(0, 16)}`;
4187
+ }
4188
+ function sanitizeCacheNamespace(ns) {
4189
+ return ns.replace(/[^a-z0-9_-]/gi, "_").replace(/^_+|_+$/g, "") || "cache";
4190
+ }
4191
+ var DiskCache = class {
4192
+ root;
4193
+ ns;
4194
+ enabled = false;
4195
+ constructor(opts) {
4196
+ this.root = opts.root;
4197
+ this.ns = sanitizeCacheNamespace(opts.namespace);
4198
+ this.enabled = !!opts.root;
4199
+ }
4200
+ pathFor(key) {
4201
+ if (!/^[a-f0-9]{16,128}$/i.test(key)) {
4202
+ throw new Error("DiskCache key must be a hex digest");
4203
+ }
4204
+ return join5(this.root, this.ns, key.slice(0, 2), `${key}.json`);
4205
+ }
4206
+ get(key) {
4207
+ if (!this.enabled || !this.root) return void 0;
4208
+ try {
4209
+ const p = this.pathFor(key);
4210
+ if (!existsSync7(p)) return void 0;
4211
+ const raw = readFileSync6(p, "utf8");
4212
+ const parsed = JSON.parse(raw);
4213
+ if (parsed?.abi !== ANALYSIS_ABI) return void 0;
4214
+ return parsed.value;
4215
+ } catch {
4216
+ return void 0;
4217
+ }
4218
+ }
4219
+ set(key, value) {
4220
+ if (!this.enabled || !this.root) return;
4221
+ try {
4222
+ const p = this.pathFor(key);
4223
+ mkdirSync(dirname10(p), { recursive: true });
4224
+ writeFileSync(p, JSON.stringify({ abi: ANALYSIS_ABI, value }), "utf8");
4225
+ } catch {
4226
+ }
4227
+ }
4228
+ clearNamespace() {
4229
+ if (!this.enabled || !this.root) return;
4230
+ try {
4231
+ rmSync(join5(this.root, this.ns), { recursive: true, force: true });
4232
+ } catch {
4233
+ }
4234
+ }
4235
+ };
4236
+ function checkCacheKey(filePath, source, opts) {
4237
+ const rel = relativizePath(filePath, opts.projectDir);
4238
+ const sidecarSha = opts.sidecarContent != null ? sha256Hex(opts.sidecarContent) : "nosidecar";
4239
+ const depSeg = (opts.depContents ?? []).map(
4240
+ (d) => `${relativizePath(d.path, opts.projectDir)}\0${d.content != null ? sha256Hex(d.content) : "miss"}`
4241
+ ).join("\n");
4242
+ const envSeg = (opts.projectEnvNames ?? []).length > 0 ? [...opts.projectEnvNames ?? []].sort().join(",") : "-";
4243
+ const cfgSeg = opts.analysisCfg ? `${opts.analysisCfg.mode ?? "-"}|${opts.analysisCfg.evalMissingSlot ?? "-"}|${opts.analysisCfg.callSiteBudget ?? "-"}` : "-";
4244
+ return sha256Hex(
4245
+ [
4246
+ ANALYSIS_ABI,
4247
+ rel,
4248
+ opts.autoBind ? "ab1" : "ab0",
4249
+ sha256Hex(source),
4250
+ sidecarSha,
4251
+ depSeg,
4252
+ envSeg,
4253
+ cfgSeg
4254
+ ].join("\0")
4255
+ );
4256
+ }
4257
+ function ifaceCacheKey(filePath, source, opts) {
4258
+ const rel = relativizePath(filePath, opts.projectDir);
4259
+ const sidecarSeg = opts.autoBind && opts.sidecarSource !== void 0 ? `sc:${sha256Hex(opts.sidecarSource)}` : "sc0";
4260
+ const depSeg = (opts.depContents ?? []).map(
4261
+ (d) => `${relativizePath(d.path, opts.projectDir)}\0${d.content != null ? sha256Hex(d.content) : "miss"}`
4262
+ ).join("\n");
4263
+ const envSeg = (opts.projectEnvNames ?? []).length > 0 ? [...opts.projectEnvNames ?? []].sort().join(",") : "-";
4264
+ return sha256Hex(
4265
+ [
4266
+ ANALYSIS_ABI,
4267
+ "iface",
4268
+ rel,
4269
+ opts.autoBind ? "ab1" : "ab0",
4270
+ sha256Hex(source),
4271
+ sidecarSeg,
4272
+ depSeg,
4273
+ envSeg
4274
+ ].join("\0")
4275
+ );
4276
+ }
4277
+ function extractNudoImportSpecs(source) {
4278
+ const specs = /* @__PURE__ */ new Set();
4279
+ const named = /@nudo:import\s*\{[^}]*\}\s*from\s*["']([^"']+)["']/g;
4280
+ const ns = /@nudo:import\s+\*\s+as\s+\w+\s+from\s*["']([^"']+)["']/g;
4281
+ let m;
4282
+ while (m = named.exec(source)) specs.add(m[1]);
4283
+ while (m = ns.exec(source)) specs.add(m[1]);
4284
+ return [...specs];
4285
+ }
4286
+
4287
+ // src/dep-contents.ts
4288
+ import { loadModuleDepsFingerprint as loadModuleDepsFingerprint3 } from "@nudojs/core";
4289
+ function collectLoadDepContents(filePath, source, loadModule) {
4290
+ const fp = loadModuleDepsFingerprint3(source, loadModule, filePath);
4291
+ const depContents = fp.contents.length > 0 ? fp.contents : fp.paths.map((path) => ({ path, content: null }));
4292
+ return { depContents, truncated: fp.truncated };
4293
+ }
4294
+
3690
4295
  // src/harvest-node.ts
3691
- import { existsSync as existsSync5 } from "fs";
4296
+ import { existsSync as existsSync8 } from "fs";
3692
4297
  import { harvestDts as harvestDts2 } from "@nudojs/harvester";
3693
4298
  function harvestNodeTypes(fromDir, maxFiles = 12, maxMs = 2500) {
3694
4299
  const root = resolvePackageRoot("@types/node", fromDir) ?? resolvePackageRoot("node", fromDir);
3695
- if (!root || !existsSync5(root)) {
4300
+ if (!root || !existsSync8(root)) {
3696
4301
  return { ok: false, error: "@types/node not found" };
3697
4302
  }
3698
4303
  const dts = collectDtsFiles(root, maxFiles);
@@ -3712,6 +4317,9 @@ function summarizeNodeEnv(env) {
3712
4317
 
3713
4318
  // src/semantic-tokens.ts
3714
4319
  import { parse as parse7 } from "@nudojs/parser";
4320
+ import {
4321
+ interfaceTierOf as interfaceTierOf2
4322
+ } from "@nudojs/core";
3715
4323
  var SEMANTIC_TOKEN_TYPES = [
3716
4324
  "function",
3717
4325
  "variable",
@@ -3729,7 +4337,13 @@ var SEMANTIC_TOKEN_MODIFIERS = [
3729
4337
  "declaration",
3730
4338
  "readonly",
3731
4339
  "deprecated",
3732
- "unreachable"
4340
+ "unreachable",
4341
+ /** handwritten:显式契约(侧车手写 / @nudo:refine) */
4342
+ "contract",
4343
+ /** generated:侧车 @generated 段 */
4344
+ "generated",
4345
+ /** derived:implicit 展示档(非义务契约) */
4346
+ "derived"
3733
4347
  ];
3734
4348
  function encodeSemanticTokens(tokens) {
3735
4349
  const result = [];
@@ -3750,6 +4364,12 @@ var TYPE_PARAMETER = SEMANTIC_TOKEN_TYPES.indexOf("parameter");
3750
4364
  var TYPE_PROPERTY = SEMANTIC_TOKEN_TYPES.indexOf("property");
3751
4365
  var TYPE_METHOD = SEMANTIC_TOKEN_TYPES.indexOf("method");
3752
4366
  var MOD_DECLARATION = 1 << SEMANTIC_TOKEN_MODIFIERS.indexOf("declaration");
4367
+ var MOD_CONTRACT = 1 << SEMANTIC_TOKEN_MODIFIERS.indexOf("contract");
4368
+ var MOD_GENERATED = 1 << SEMANTIC_TOKEN_MODIFIERS.indexOf("generated");
4369
+ var MOD_DERIVED = 1 << SEMANTIC_TOKEN_MODIFIERS.indexOf("derived");
4370
+ function interfaceTierModifierBit(src) {
4371
+ return src === "handwritten" ? MOD_CONTRACT : src === "generated" ? MOD_GENERATED : MOD_DERIVED;
4372
+ }
3753
4373
  function collectFunctionBindingNames(filePath, source, _ast) {
3754
4374
  try {
3755
4375
  const binds = collectAbsBindingsFromGraph(source, filePath);
@@ -3764,7 +4384,7 @@ function collectFunctionBindingNames(filePath, source, _ast) {
3764
4384
  }
3765
4385
  return /* @__PURE__ */ new Set();
3766
4386
  }
3767
- function buildSemanticTokens(filePath, source) {
4387
+ function buildSemanticTokens(filePath, source, opts) {
3768
4388
  let ast;
3769
4389
  try {
3770
4390
  ast = parse7(source);
@@ -3774,6 +4394,7 @@ function buildSemanticTokens(filePath, source) {
3774
4394
  const functionNames = collectFunctionBindingNames(filePath, source, ast);
3775
4395
  const program = ast.program ?? ast;
3776
4396
  const topLevelDeclarators = /* @__PURE__ */ new Set();
4397
+ const topLevelFnDeclNodes = /* @__PURE__ */ new Set();
3777
4398
  for (const stmt of program.body ?? []) {
3778
4399
  const decl = stmt.type === "ExportNamedDeclaration" || stmt.type === "ExportDefaultDeclaration" ? stmt.declaration ?? null : stmt;
3779
4400
  if (decl && decl.type === "VariableDeclaration") {
@@ -3781,9 +4402,30 @@ function buildSemanticTokens(filePath, source) {
3781
4402
  topLevelDeclarators.add(d);
3782
4403
  }
3783
4404
  }
4405
+ if (decl && decl.type === "FunctionDeclaration" && decl.id) {
4406
+ topLevelFnDeclNodes.add(decl);
4407
+ }
3784
4408
  }
4409
+ const tierOpts = {
4410
+ ...opts?.loadModule ? { loadModule: opts.loadModule } : {},
4411
+ ...opts?.autoBind !== void 0 ? { autoBind: opts.autoBind } : {}
4412
+ };
4413
+ const tierModCache = /* @__PURE__ */ new Map();
4414
+ const tierModFor = (name) => {
4415
+ const cached = tierModCache.get(name);
4416
+ if (cached !== void 0) return cached;
4417
+ let bit = 0;
4418
+ try {
4419
+ const tier = interfaceTierOf2(source, name, filePath, tierOpts);
4420
+ if (tier) bit = interfaceTierModifierBit(tier.source);
4421
+ } catch {
4422
+ bit = 0;
4423
+ }
4424
+ tierModCache.set(name, bit);
4425
+ return bit;
4426
+ };
3785
4427
  const tokens = [];
3786
- const pushIdentifier = (id, typeIndex) => {
4428
+ const pushIdentifier = (id, typeIndex, extraMod = 0) => {
3787
4429
  const loc = id.loc;
3788
4430
  const name = id.name;
3789
4431
  if (!loc || typeof name !== "string") return;
@@ -3792,7 +4434,7 @@ function buildSemanticTokens(filePath, source) {
3792
4434
  char: loc.start.column,
3793
4435
  length: name.length,
3794
4436
  typeIndex,
3795
- modifierBitmask: MOD_DECLARATION
4437
+ modifierBitmask: MOD_DECLARATION | extraMod
3796
4438
  });
3797
4439
  };
3798
4440
  const collectParams = (params) => {
@@ -3815,15 +4457,23 @@ function buildSemanticTokens(filePath, source) {
3815
4457
  const id = n.id;
3816
4458
  if (id?.type === "Identifier") {
3817
4459
  const isTopLevel = topLevelDeclarators.has(node);
3818
- const typeIndex = isTopLevel && isFunctionValue(id.name) ? TYPE_FUNCTION : TYPE_VARIABLE;
3819
- pushIdentifier(id, typeIndex);
4460
+ const name = id.name;
4461
+ const isFn = isTopLevel && isFunctionValue(name);
4462
+ const typeIndex = isFn ? TYPE_FUNCTION : TYPE_VARIABLE;
4463
+ const extraMod = isFn ? tierModFor(name) : 0;
4464
+ pushIdentifier(id, typeIndex, extraMod);
3820
4465
  }
3821
4466
  break;
3822
4467
  }
3823
4468
  case "FunctionDeclaration":
3824
4469
  case "FunctionExpression": {
3825
4470
  const id = n.id;
3826
- if (id?.type === "Identifier") pushIdentifier(id, TYPE_FUNCTION);
4471
+ if (id?.type === "Identifier") {
4472
+ const name = id.name;
4473
+ const isTopLevelFnDecl = n.type === "FunctionDeclaration" && topLevelFnDeclNodes.has(node);
4474
+ const extraMod = isTopLevelFnDecl ? tierModFor(name) : 0;
4475
+ pushIdentifier(id, TYPE_FUNCTION, extraMod);
4476
+ }
3827
4477
  collectParams(n.params ?? []);
3828
4478
  break;
3829
4479
  }
@@ -3871,14 +4521,102 @@ import {
3871
4521
  litValue as litValue3,
3872
4522
  abs as makeAbs2,
3873
4523
  isTemplateLike,
3874
- templatePartsOf
4524
+ templatePartsOf,
4525
+ collectAbsFreeVars
3875
4526
  } from "@nudojs/core";
3876
- function wrapComplexAbs(a) {
3877
- const ts = absToTSType(a);
3878
- if (a.shape.k === "sum") return `(${ts})`;
4527
+ function tsTypeParamName(id) {
4528
+ let n = id.replace(/[^A-Za-z0-9_$]/g, "_");
4529
+ if (!/^[A-Za-z_$]/.test(n)) n = `T_${n}`;
4530
+ if (n.length === 0) n = "T";
4531
+ return n;
4532
+ }
4533
+ function wrapComplexAbs(a, typeVars) {
4534
+ const ts = absToTSType(a, typeVars);
4535
+ if (a.shape.k === "sum" || a.shape.k === "fn") return `(${ts})`;
3879
4536
  return ts;
3880
4537
  }
3881
- function absToTSType(a) {
4538
+ function wrapUnionMember(a, typeVars) {
4539
+ const ts = absToTSType(a, typeVars);
4540
+ if (a.shape.k === "fn") return `(${ts})`;
4541
+ return ts;
4542
+ }
4543
+ var TS_PARAM_RESERVED = /* @__PURE__ */ new Set([
4544
+ "break",
4545
+ "case",
4546
+ "catch",
4547
+ "class",
4548
+ "const",
4549
+ "continue",
4550
+ "debugger",
4551
+ "default",
4552
+ "delete",
4553
+ "do",
4554
+ "else",
4555
+ "enum",
4556
+ "export",
4557
+ "extends",
4558
+ "false",
4559
+ "finally",
4560
+ "for",
4561
+ "function",
4562
+ "if",
4563
+ "import",
4564
+ "in",
4565
+ "instanceof",
4566
+ "new",
4567
+ "null",
4568
+ "return",
4569
+ "super",
4570
+ "switch",
4571
+ "this",
4572
+ "throw",
4573
+ "true",
4574
+ "try",
4575
+ "typeof",
4576
+ "var",
4577
+ "void",
4578
+ "while",
4579
+ "with",
4580
+ "yield",
4581
+ "let",
4582
+ "static",
4583
+ "await",
4584
+ "implements",
4585
+ "interface",
4586
+ "package",
4587
+ "private",
4588
+ "protected",
4589
+ "public",
4590
+ "arguments",
4591
+ "eval",
4592
+ "constructor"
4593
+ ]);
4594
+ function isTsIdent(name) {
4595
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) && !TS_PARAM_RESERVED.has(name);
4596
+ }
4597
+ function sanitizeParamName(name, index) {
4598
+ if (name.startsWith("...")) {
4599
+ const rest = name.slice(3);
4600
+ if (isTsIdent(rest)) return name;
4601
+ return `...arg${index}`;
4602
+ }
4603
+ if (isTsIdent(name)) return name;
4604
+ return `arg${index}`;
4605
+ }
4606
+ function formatPropKey(k) {
4607
+ if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(k)) return k;
4608
+ if (/^\d+$/.test(k)) return k;
4609
+ return JSON.stringify(k);
4610
+ }
4611
+ function absToTSType(a, typeVars) {
4612
+ if (a.shape.k === "any" && a.term?.op === "var" && typeVars) {
4613
+ const mapped = typeVars.get(a.term.id);
4614
+ if (mapped) return mapped;
4615
+ }
4616
+ if (a.shape.k === "arr" && a.shape.element.shape.k === "any" && a.shape.element.term?.op === "var" && typeVars) {
4617
+ const mapped = typeVars.get(a.shape.element.term.id);
4618
+ if (mapped) return `${mapped}[]`;
4619
+ }
3882
4620
  if (a.term?.op === "lit") {
3883
4621
  const v = a.term.value;
3884
4622
  if (v === null) return "null";
@@ -3894,7 +4632,7 @@ function absToTSType(a) {
3894
4632
  const inner = parts.map((p) => {
3895
4633
  const lv = litValue3(p);
3896
4634
  if (typeof lv === "string") return lv;
3897
- return `\${${absToTSType(p)}}`;
4635
+ return `\${${absToTSType(p, typeVars)}}`;
3898
4636
  }).join("");
3899
4637
  return `\`${inner}\``;
3900
4638
  }
@@ -3908,34 +4646,50 @@ function absToTSType(a) {
3908
4646
  return a.shape.type;
3909
4647
  case "obj": {
3910
4648
  const entries = Object.entries(a.shape.slots).map(([k, slot]) => {
3911
- const inner = absToTSType(slot.value);
4649
+ const inner = absToTSType(slot.value, typeVars);
3912
4650
  if (slot.optional) {
3913
- const t = slot.value.shape.k === "sum" ? `(${inner})` : inner;
3914
- return `${k}: ${t} | undefined`;
4651
+ const t = wrapComplexAbs(slot.value, typeVars);
4652
+ return `${formatPropKey(k)}: ${t} | undefined`;
3915
4653
  }
3916
- return `${k}: ${inner}`;
4654
+ return `${formatPropKey(k)}: ${inner}`;
3917
4655
  });
3918
4656
  if (entries.length === 0) return "{}";
3919
4657
  return `{ ${entries.join("; ")} }`;
3920
4658
  }
3921
4659
  case "arr":
3922
- return `${wrapComplexAbs(a.shape.element)}[]`;
4660
+ return `${wrapComplexAbs(a.shape.element, typeVars)}[]`;
3923
4661
  case "tuple": {
3924
- const inner = a.shape.elements.map(absToTSType).join(", ");
3925
- return `[${inner}]`;
4662
+ const parts = a.shape.elements.map((e) => absToTSType(e, typeVars));
4663
+ if (a.shape.rest) {
4664
+ const rest = a.shape.rest;
4665
+ const restTs = rest.shape.k === "arr" ? absToTSType(rest, typeVars) : `${absToTSType(rest, typeVars)}[]`;
4666
+ parts.push(`...${restTs}`);
4667
+ }
4668
+ return `[${parts.join(", ")}]`;
3926
4669
  }
3927
4670
  case "fn": {
3928
- const params = a.shape.params.map((p) => `${p}: unknown`).join(", ");
3929
- const ret = a.shape.returnType ? absToTSType(a.shape.returnType) : "unknown";
4671
+ const paramTypes = a.shape.paramTypes;
4672
+ const params = a.shape.params.map((p, i) => {
4673
+ const isRest = p.startsWith("...");
4674
+ const name = sanitizeParamName(p, i);
4675
+ const pt = paramTypes?.[i];
4676
+ let typeStr;
4677
+ if (pt) typeStr = absToTSType(pt, typeVars);
4678
+ else if (isRest) typeStr = "unknown[]";
4679
+ else typeStr = "unknown";
4680
+ return `${name}: ${typeStr}`;
4681
+ }).join(", ");
4682
+ const ret = a.shape.returnType ? absToTSType(a.shape.returnType, typeVars) : "unknown";
3930
4683
  return `(${params}) => ${ret}`;
3931
4684
  }
3932
4685
  case "brand":
3933
- return a.shape.name;
4686
+ return isTsIdent(a.shape.name) || /^[A-Z][A-Za-z0-9_$]*$/.test(a.shape.name) ? a.shape.name : "unknown";
3934
4687
  case "eff":
3935
- if (a.shape.eff === "promise") return `Promise<${absToTSType(a.shape.inner)}>`;
3936
- return absToTSType(a.shape.inner);
4688
+ if (a.shape.eff === "promise")
4689
+ return `Promise<${absToTSType(a.shape.inner, typeVars)}>`;
4690
+ return absToTSType(a.shape.inner, typeVars);
3937
4691
  case "sum": {
3938
- const parts = a.shape.members.map(absToTSType).filter((p) => p !== "never");
4692
+ const parts = a.shape.members.map((m) => wrapUnionMember(m, typeVars)).filter((p) => p !== "never");
3939
4693
  const uniq = [...new Set(parts)];
3940
4694
  if (uniq.length === 0) return "never";
3941
4695
  if (uniq.length === 1) return uniq[0];
@@ -4067,13 +4821,18 @@ function computeMainSignature(fn) {
4067
4821
  }
4068
4822
  const typeStr = paramTypeFromAbs(members);
4069
4823
  let name = getParamName(fn, i);
4824
+ const isRest = name.startsWith("...");
4825
+ const bare = isRest ? name.slice(3) : name;
4826
+ if (!isTsIdent(bare)) {
4827
+ name = isRest ? `...arg${i}` : `arg${i}`;
4828
+ }
4070
4829
  if (usedNames.has(name)) {
4071
4830
  let n = 2;
4072
4831
  while (usedNames.has(`${name}${n}`)) n++;
4073
4832
  name = `${name}${n}`;
4074
4833
  }
4075
4834
  usedNames.add(name);
4076
- const optional = i >= minArity && !name.startsWith("...");
4835
+ const optional = i >= minArity && !isRest;
4077
4836
  params.push(optional ? `${name}?: ${typeStr}` : `${name}: ${typeStr}`);
4078
4837
  paramNames.push(name);
4079
4838
  paramTypes.push(typeStr);
@@ -4088,7 +4847,7 @@ function generateJSDoc(fn, sig) {
4088
4847
  for (const c of fn.cases) {
4089
4848
  const preciseDiffers = c.argAbs.length !== sig.paramTypes.length || c.argAbs.some((a, i) => absToTSType(a) !== sig.paramTypes[i]) || absToTSType(c.abs) !== sig.returnType;
4090
4849
  if (!preciseDiffers) continue;
4091
- const argsStr = c.argAbs.map(absToTSType).join(", ");
4850
+ const argsStr = c.argAbs.map((a) => absToTSType(a)).join(", ");
4092
4851
  lines.push(` * Case: ${c.name} (${argsStr}) => ${absToTSType(c.abs)}`);
4093
4852
  }
4094
4853
  for (let i = 0; i < sig.paramTypes.length; i++) {
@@ -4098,8 +4857,103 @@ function generateJSDoc(fn, sig) {
4098
4857
  lines.push(" */");
4099
4858
  return lines.join("\n");
4100
4859
  }
4860
+ function computeHofSignature(fn) {
4861
+ const hof = fn.hof;
4862
+ if (!hof) return void 0;
4863
+ const byParam = /* @__PURE__ */ new Map();
4864
+ for (const s of hof.entryShapes ?? []) byParam.set(s.param, s.abs);
4865
+ for (const r of hof.fnRels ?? []) byParam.set(r.param, r.abs);
4866
+ if (byParam.size === 0 && !hof.symbolic) return void 0;
4867
+ if (fn.cases.length > 0) {
4868
+ for (const [param] of byParam) {
4869
+ const idx = fn.paramNames.indexOf(param);
4870
+ if (idx < 0) continue;
4871
+ const caseType = paramTypeFromAbs(
4872
+ fn.cases.map((c) => c.argAbs[idx]).filter((a) => !!a)
4873
+ );
4874
+ if (caseType !== "unknown" && caseType !== "unknown[]") {
4875
+ return void 0;
4876
+ }
4877
+ }
4878
+ }
4879
+ const arity = Math.max(
4880
+ fn.paramNames.length,
4881
+ ...[...byParam.keys()].map((p) => fn.paramNames.indexOf(p) + 1),
4882
+ 0
4883
+ );
4884
+ if (arity === 0 && !hof.symbolic) return void 0;
4885
+ const free = /* @__PURE__ */ new Set();
4886
+ for (const a of byParam.values()) {
4887
+ for (const id of collectAbsFreeVars(a)) free.add(id);
4888
+ }
4889
+ const retAbs = hof.symbolic ?? fn.combinedAbs;
4890
+ if (retAbs) {
4891
+ for (const id of collectAbsFreeVars(retAbs)) free.add(id);
4892
+ }
4893
+ if (free.size === 0) return void 0;
4894
+ const typeVars = /* @__PURE__ */ new Map();
4895
+ const used = /* @__PURE__ */ new Set();
4896
+ const typeParams = [];
4897
+ for (const id of [...free].sort()) {
4898
+ let n = tsTypeParamName(id);
4899
+ if (used.has(n) || TS_PARAM_RESERVED.has(n)) {
4900
+ let i = 2;
4901
+ while (used.has(`${n}${i}`)) i++;
4902
+ n = `${n}${i}`;
4903
+ }
4904
+ used.add(n);
4905
+ typeVars.set(id, n);
4906
+ typeParams.push(n);
4907
+ }
4908
+ const params = [];
4909
+ const paramNames = [];
4910
+ const paramTypes = [];
4911
+ const usedNames = /* @__PURE__ */ new Set();
4912
+ for (let i = 0; i < arity; i++) {
4913
+ const rawName = getParamName(fn, i);
4914
+ const isRest = rawName.startsWith("...");
4915
+ let name = sanitizeParamName(rawName, i);
4916
+ if (usedNames.has(name)) {
4917
+ let n = 2;
4918
+ while (usedNames.has(`${name}${n}`)) n++;
4919
+ name = isRest && name.startsWith("...") ? `...${name.slice(3)}${n}` : `${name}${n}`;
4920
+ }
4921
+ usedNames.add(name);
4922
+ const promoted = byParam.get(fn.paramNames[i] ?? rawName) ?? byParam.get(rawName);
4923
+ const typeStr = promoted ? absToTSType(promoted, typeVars) : isRest ? "unknown[]" : "unknown";
4924
+ params.push(`${name}: ${typeStr}`);
4925
+ paramNames.push(name);
4926
+ paramTypes.push(typeStr);
4927
+ }
4928
+ const returnType = retAbs ? absToTSType(retAbs, typeVars) : "unknown";
4929
+ return { typeParams, params, paramNames, paramTypes, returnType };
4930
+ }
4101
4931
  function generateFunctionDtsLines(fn) {
4102
- if (fn.noDeclaration) return [];
4932
+ let emitFn = fn;
4933
+ if (fn.noDeclaration) {
4934
+ const m = /^([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)$/.exec(fn.name);
4935
+ if (!m) return [];
4936
+ emitFn = { ...fn, name: `${m[1]}_${m[2]}`, noDeclaration: false };
4937
+ }
4938
+ fn = emitFn;
4939
+ const hofSig = computeHofSignature(fn);
4940
+ if (hofSig) {
4941
+ const lines2 = [];
4942
+ if (fn.cases.length > 0) {
4943
+ const jsdoc2 = generateJSDoc(fn, {
4944
+ params: hofSig.params,
4945
+ paramNames: hofSig.paramNames,
4946
+ paramTypes: hofSig.paramTypes,
4947
+ returnType: hofSig.returnType
4948
+ });
4949
+ if (jsdoc2) lines2.push(jsdoc2);
4950
+ }
4951
+ const tparams = hofSig.typeParams.length > 0 ? `<${hofSig.typeParams.join(", ")}>` : "";
4952
+ lines2.push(
4953
+ `export declare function ${fn.name}${tparams}(${hofSig.params.join(", ")}): ${hofSig.returnType};`
4954
+ );
4955
+ return lines2;
4956
+ }
4103
4957
  if (fn.cases.length === 0) {
4104
4958
  const retAbs = fn.combinedAbs;
4105
4959
  if (retAbs) {
@@ -4108,7 +4962,6 @@ function generateFunctionDtsLines(fn) {
4108
4962
  ];
4109
4963
  }
4110
4964
  return [];
4111
- return [];
4112
4965
  }
4113
4966
  const sig = computeMainSignature(fn);
4114
4967
  const jsdoc = generateJSDoc(fn, sig);
@@ -4539,8 +5392,8 @@ function unifiedDiff(a, b, path) {
4539
5392
  }
4540
5393
 
4541
5394
  // src/interface-surface.ts
4542
- import { readFileSync as readFileSync6 } from "fs";
4543
- import { dirname as dirname8, resolve as resolve6 } from "path";
5395
+ import { readFileSync as readFileSync7, existsSync as existsSync9 } from "fs";
5396
+ import { dirname as dirname11, resolve as resolve7 } from "path";
4544
5397
  import {
4545
5398
  effectiveInterface,
4546
5399
  formatConstraint,
@@ -4548,9 +5401,13 @@ import {
4548
5401
  interfaceDiagCount,
4549
5402
  localNamedExports,
4550
5403
  refineDiagCount,
5404
+ sidecarPathOf as sidecarPathOf2,
4551
5405
  takeInterfaceDiagsSince,
4552
5406
  takeRefineDiagsSince
4553
5407
  } from "@nudojs/core";
5408
+ function collectDepContents(filePath, source, loadModule) {
5409
+ return collectLoadDepContents(filePath, source, loadModule ?? defaultLoadModule);
5410
+ }
4554
5411
  function formatInterfaceSurfaceLine(e) {
4555
5412
  const params = `(${e.params.map((p) => `${p.name}: ${p.display}`).join(", ")})`;
4556
5413
  let line = ` ${e.fn} [${e.source}] ${params}`;
@@ -4565,19 +5422,92 @@ function absToImplicitDisplay(a) {
4565
5422
  return "unknown";
4566
5423
  }
4567
5424
  }
5425
+ function constraintToJson(c) {
5426
+ return JSON.parse(JSON.stringify(c));
5427
+ }
5428
+ function cachedToEffective(e) {
5429
+ return {
5430
+ fnName: e.fnName,
5431
+ params: e.params.map((p) => ({
5432
+ param: p.param,
5433
+ constraint: p.constraint
5434
+ })),
5435
+ ...e.returns ? { returns: { constraint: e.returns.constraint } } : {},
5436
+ source: e.source,
5437
+ ...e.conflict ? { conflict: e.conflict } : {}
5438
+ };
5439
+ }
4568
5440
  async function interfaceSurface(filePath, opts = {}) {
4569
- const abs = resolve6(filePath);
4570
- const source = readFileSync6(abs, "utf-8");
5441
+ const abs = resolve7(filePath);
5442
+ const source = opts.source ?? readFileSync7(abs, "utf-8");
5443
+ const fromBuffer = opts.source !== void 0;
4571
5444
  const ifaceSince = interfaceDiagCount();
4572
5445
  const refineSince = refineDiagCount();
4573
- const autoBind = opts.autoBind ?? interfaceConfig(findProjectConfig(dirname8(abs))?.config).autoBind;
5446
+ const proj = findProjectConfig(dirname11(abs));
5447
+ const autoBind = opts.autoBind ?? interfaceConfig(proj?.config).autoBind;
4574
5448
  const loadModule = opts.loadModule ?? defaultLoadModule;
4575
5449
  const exported = localNamedExports(source);
4576
5450
  const kindOf = (fnName) => exported.has(fnName) ? "export" : "local";
4577
- const analysis = await analyzeFileAsync(abs, source, void 0, opts.records);
5451
+ const analysis = await analyzeFileAsync(abs, source, void 0, opts.records, loadModule);
5452
+ let disk;
5453
+ let ifaceKey;
5454
+ let cachedTable;
5455
+ if (!opts.loadModule && !opts.records && !fromBuffer) {
5456
+ const cacheRoot = diskCacheRoot(proj?.config, proj?.projectDir);
5457
+ disk = new DiskCache({ root: cacheRoot, namespace: "iface" });
5458
+ if (disk.enabled) {
5459
+ let sidecarSource;
5460
+ try {
5461
+ const sc = sidecarPathOf2(abs);
5462
+ if (autoBind !== false) {
5463
+ const openSc = loadModule(`./${sc.slice(sc.lastIndexOf("/") + 1)}`, abs);
5464
+ if (openSc !== void 0) sidecarSource = openSc;
5465
+ else if (existsSync9(sc)) sidecarSource = readFileSync7(sc, "utf-8");
5466
+ }
5467
+ } catch {
5468
+ sidecarSource = void 0;
5469
+ }
5470
+ const dep = collectDepContents(abs, source, loadModule);
5471
+ const hasBareMiss = (dep.depContents ?? []).some((d) => d.content == null);
5472
+ if (dep.truncated || hasBareMiss) {
5473
+ ifaceKey = void 0;
5474
+ cachedTable = void 0;
5475
+ } else {
5476
+ ifaceKey = ifaceCacheKey(abs, source, {
5477
+ autoBind: autoBind !== false,
5478
+ projectDir: proj?.projectDir,
5479
+ sidecarSource,
5480
+ depContents: dep.depContents,
5481
+ projectEnvNames: proj?.config.env ?? []
5482
+ });
5483
+ cachedTable = disk.get(ifaceKey);
5484
+ }
5485
+ }
5486
+ }
4578
5487
  const entries = [];
5488
+ const freshTable = { fns: {} };
5489
+ const useCache = cachedTable !== void 0;
4579
5490
  for (const fn of analysis.functions) {
4580
- const eff = effectiveInterface(source, fn.name, { loadModule, fromFile: abs, autoBind });
5491
+ let eff;
5492
+ if (useCache) {
5493
+ const hit = cachedTable.fns[fn.name];
5494
+ eff = hit === null ? void 0 : hit ? cachedToEffective(hit) : void 0;
5495
+ } else {
5496
+ eff = effectiveInterface(source, fn.name, { loadModule, fromFile: abs, autoBind });
5497
+ if (freshTable) {
5498
+ freshTable.fns[fn.name] = eff ? {
5499
+ fnName: eff.fnName,
5500
+ params: eff.params.map((p) => ({
5501
+ param: p.param,
5502
+ constraint: constraintToJson(p.constraint)
5503
+ })),
5504
+ ...eff.returns ? { returns: { constraint: constraintToJson(eff.returns.constraint) } } : {},
5505
+ // 磁盘表保留真实分档;implicit 也可序列化(展示层用)
5506
+ source: eff.source,
5507
+ ...eff.conflict ? { conflict: eff.conflict } : {}
5508
+ } : null;
5509
+ }
5510
+ }
4581
5511
  if (eff) {
4582
5512
  entries.push({
4583
5513
  fn: fn.name,
@@ -4608,14 +5538,20 @@ async function interfaceSurface(filePath, opts = {}) {
4608
5538
  }
4609
5539
  entries.push({ fn: fn.name, kind: kindOf(fn.name), source: "implicit", params, returns: ret });
4610
5540
  }
5541
+ if (disk?.enabled && ifaceKey && !useCache && analysis.functions.length > 0) {
5542
+ try {
5543
+ disk.set(ifaceKey, freshTable);
5544
+ } catch {
5545
+ }
5546
+ }
4611
5547
  takeInterfaceDiagsSince(ifaceSince);
4612
5548
  takeRefineDiagsSince(refineSince);
4613
5549
  return entries;
4614
5550
  }
4615
5551
 
4616
5552
  // src/interface-emitter.ts
4617
- import { existsSync as existsSync6, readFileSync as readFileSync7, writeFileSync, renameSync, unlinkSync } from "fs";
4618
- import { basename as basename2, dirname as dirname9, relative, resolve as resolve7 } from "path";
5553
+ import { existsSync as existsSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync2, renameSync, unlinkSync } from "fs";
5554
+ import { basename as basename2, dirname as dirname12, relative as relative2, resolve as resolve8 } from "path";
4619
5555
  import {
4620
5556
  execNudoModule,
4621
5557
  formatConstraint as formatConstraint2,
@@ -4626,20 +5562,20 @@ import {
4626
5562
  localNamedExports as localNamedExports2,
4627
5563
  parseSource as parseSource2,
4628
5564
  refineDiagCount as refineDiagCount2,
4629
- sidecarPathOf,
5565
+ sidecarPathOf as sidecarPathOf3,
4630
5566
  takeInterfaceDiagsSince as takeInterfaceDiagsSince2,
4631
5567
  takeRefineDiagsSince as takeRefineDiagsSince2
4632
5568
  } from "@nudojs/core";
4633
5569
  import { randomBytes } from "crypto";
4634
5570
  var GENERATED_HEADER = "// @generated by nudo \u2014 do not edit; regenerate with `nudo interface --emit`";
4635
5571
  async function emitInterface(filePath, opts) {
4636
- const abs = resolve7(filePath);
4637
- if (isNodeModulesPath(abs) || isNodeModulesPath(sidecarPathOf(abs))) {
5572
+ const abs = resolve8(filePath);
5573
+ if (isNodeModulesPath(abs) || isNodeModulesPath(sidecarPathOf3(abs))) {
4638
5574
  throw new Error(
4639
5575
  `emit target '${abs}' is inside node_modules; contract sidecars are never written there`
4640
5576
  );
4641
5577
  }
4642
- const proj = findProjectConfig(dirname9(abs));
5578
+ const proj = findProjectConfig(dirname12(abs));
4643
5579
  const allow = interfaceConfig(proj?.config).emit;
4644
5580
  if (!matchesEmitAllowlist(abs, proj?.projectDir, allow)) {
4645
5581
  return {
@@ -4650,28 +5586,34 @@ async function emitInterface(filePath, opts) {
4650
5586
  {
4651
5587
  code: "nudo:interface-emit-denied",
4652
5588
  severity: "warning",
4653
- message: `emit target '${relative(process.cwd(), abs) || abs}' is outside package.json#nudo.interface.emit allowlist`
5589
+ message: `emit target '${relative2(process.cwd(), abs) || abs}' is outside package.json#nudo.interface.emit allowlist`
4654
5590
  }
4655
5591
  ],
4656
- sidecarPath: sidecarPathOf(abs)
5592
+ sidecarPath: sidecarPathOf3(abs)
4657
5593
  };
4658
5594
  }
4659
- const source = readFileSync7(abs, "utf-8");
5595
+ const source = opts.source ?? readFileSync8(abs, "utf-8");
4660
5596
  const ifaceSince = interfaceDiagCount2();
4661
5597
  const refineSince = refineDiagCount2();
4662
- const sidecarPath = sidecarPathOf(abs);
4663
- const sidecarSrc = existsSync6(sidecarPath) ? readFileSync7(sidecarPath, "utf-8") : "";
4664
- const srcRel = relative(dirname9(sidecarPath), abs) || basename2(abs);
5598
+ const sidecarPath = sidecarPathOf3(abs);
5599
+ const sidecarSrc = existsSync10(sidecarPath) ? readFileSync8(sidecarPath, "utf-8") : "";
5600
+ const srcRel = relative2(dirname12(sidecarPath), abs) || basename2(abs);
4665
5601
  const sections = collectGeneratedSections(sidecarSrc);
4666
5602
  const generatedNames = new Set(sections.flatMap((s) => s.names));
4667
5603
  const declared = topLevelDeclaredNames(sidecarSrc);
4668
5604
  if (declared === void 0) {
4669
5605
  throw new Error(
4670
- `sidecar '${relative(process.cwd(), sidecarPath) || sidecarPath}' is not parseable; fix it before emitting`
5606
+ `sidecar '${relative2(process.cwd(), sidecarPath) || sidecarPath}' is not parseable; fix it before emitting`
4671
5607
  );
4672
5608
  }
4673
5609
  for (const n of generatedNames) declared.delete(n);
4674
- const analysis = await analyzeFileAsync(abs, source, void 0, opts.records);
5610
+ const analysis = await analyzeFileAsync(
5611
+ abs,
5612
+ source,
5613
+ void 0,
5614
+ opts.records,
5615
+ opts.loadModule
5616
+ );
4675
5617
  const exported = localNamedExports2(source);
4676
5618
  const fnByName = /* @__PURE__ */ new Map();
4677
5619
  for (const f of analysis.functions) {
@@ -4774,15 +5716,15 @@ async function emitInterface(filePath, opts) {
4774
5716
  finalContent = joinSections(base, [...preserved, ...accepted.map((a) => a.text)]);
4775
5717
  }
4776
5718
  const changed = finalContent !== sidecarSrc;
4777
- const diff = changed ? unifiedDiff(sidecarSrc, finalContent, relative(process.cwd(), sidecarPath) || sidecarPath) : void 0;
5719
+ const diff = changed ? unifiedDiff(sidecarSrc, finalContent, relative2(process.cwd(), sidecarPath) || sidecarPath) : void 0;
4778
5720
  if (changed && !opts.dryRun) {
4779
5721
  const tmp = `${sidecarPath}.tmp-${process.pid}-${randomBytes(4).toString("hex")}`;
4780
5722
  try {
4781
- writeFileSync(tmp, finalContent, "utf-8");
5723
+ writeFileSync2(tmp, finalContent, "utf-8");
4782
5724
  renameSync(tmp, sidecarPath);
4783
5725
  } catch (e) {
4784
5726
  try {
4785
- if (existsSync6(tmp)) unlinkSync(tmp);
5727
+ if (existsSync10(tmp)) unlinkSync(tmp);
4786
5728
  } catch {
4787
5729
  }
4788
5730
  throw e;
@@ -4972,29 +5914,638 @@ function joinSections(base, sectionTexts) {
4972
5914
  return lead + normalized.join("\n");
4973
5915
  }
4974
5916
 
4975
- // src/interface-derivation.ts
4976
- import { existsSync as existsSync7, readFileSync as readFileSync8, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
4977
- import { basename as basename3, dirname as dirname10, relative as relative2, resolve as resolve8 } from "path";
5917
+ // src/interface-draft.ts
5918
+ import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync3, realpathSync as realpathSync2 } from "fs";
5919
+ import { basename as basename3, dirname as dirname13, isAbsolute as isAbsolute2, join as join6, relative as relative3 } from "path";
5920
+ import {
5921
+ effectiveInterface as effectiveInterface2,
5922
+ formatConstraint as formatConstraint3,
5923
+ formatShape as formatShape6,
5924
+ generalizeFromAst as generalizeFromAst4,
5925
+ isIntFlag,
5926
+ joinThenProject as joinThenProject2,
5927
+ localNamedExports as localNamedExports3,
5928
+ sidecarPathOf as sidecarPathOf4
5929
+ } from "@nudojs/core";
4978
5930
  import { parse as parse9 } from "@nudojs/parser";
5931
+ function collectParamBodyAccesses(source) {
5932
+ const out = /* @__PURE__ */ new Map();
5933
+ let ast;
5934
+ try {
5935
+ ast = parse9(source);
5936
+ } catch {
5937
+ return out;
5938
+ }
5939
+ const keyOf = (node) => {
5940
+ if (node.type === "Identifier") return node.name;
5941
+ if (node.type === "StringLiteral") return node.value;
5942
+ return void 0;
5943
+ };
5944
+ const visitFn = (fnName, fnNode, paramNames) => {
5945
+ if (paramNames.size === 0) return;
5946
+ const byParam = /* @__PURE__ */ new Map();
5947
+ const walk = (node, shadowed) => {
5948
+ if (!node || typeof node !== "object") return;
5949
+ const n = node;
5950
+ if ((n.type === "VariableDeclarator" || n.type === "FunctionDeclaration") && n.id?.type === "Identifier") {
5951
+ const id = n.id.name;
5952
+ if (paramNames.has(id)) {
5953
+ shadowed = new Set(shadowed).add(id);
5954
+ }
5955
+ }
5956
+ if (n.type === "MemberExpression" || n.type === "OptionalMemberExpression") {
5957
+ const obj = n.object;
5958
+ const prop = n.property;
5959
+ const computed = n.computed === true;
5960
+ if (obj?.type === "Identifier" && paramNames.has(obj.name) && !shadowed.has(obj.name) && prop && !computed) {
5961
+ const key = keyOf(prop);
5962
+ const pname = obj.name;
5963
+ if (key !== void 0) {
5964
+ if (!byParam.has(pname)) byParam.set(pname, /* @__PURE__ */ new Set());
5965
+ byParam.get(pname).add(key);
5966
+ }
5967
+ }
5968
+ }
5969
+ for (const k of Object.keys(n)) {
5970
+ if (k === "loc" || k === "start" || k === "end") continue;
5971
+ const child = n[k];
5972
+ if (Array.isArray(child)) {
5973
+ for (const item of child) walk(item, shadowed);
5974
+ } else if (child && typeof child === "object") {
5975
+ walk(child, shadowed);
5976
+ }
5977
+ }
5978
+ };
5979
+ walk(fnNode, /* @__PURE__ */ new Set());
5980
+ if (byParam.size > 0) out.set(fnName, byParam);
5981
+ };
5982
+ const paramSet = (fnNode) => {
5983
+ const names = /* @__PURE__ */ new Set();
5984
+ const params = fnNode.params ?? [];
5985
+ for (const p of params) {
5986
+ if (!p) continue;
5987
+ if (p.type === "Identifier") names.add(p.name);
5988
+ else if (p.type === "AssignmentPattern" && p.left?.type === "Identifier") {
5989
+ names.add(p.left.name);
5990
+ } else if (p.type === "RestElement" && p.argument?.type === "Identifier") {
5991
+ names.add(p.argument.name);
5992
+ } else if (p.type === "ObjectPattern") {
5993
+ for (const prop of p.properties ?? []) {
5994
+ if (prop.type === "ObjectProperty") {
5995
+ const v = prop.value;
5996
+ if (v.type === "Identifier") names.add(v.name);
5997
+ else if (v.type === "AssignmentPattern" && v.left?.type === "Identifier") {
5998
+ names.add(v.left.name);
5999
+ }
6000
+ }
6001
+ }
6002
+ }
6003
+ }
6004
+ return names;
6005
+ };
6006
+ const visitClassMethods = (className, classNode) => {
6007
+ const body = classNode.body?.body ?? [];
6008
+ for (const m of body) {
6009
+ const mem = m;
6010
+ const isMethod = mem.type === "MethodDefinition" || mem.type === "ClassMethod" || mem.type === "TSDeclareMethod";
6011
+ if (!isMethod || mem.static) continue;
6012
+ if (mem.kind && mem.kind !== "method") continue;
6013
+ const keyName = mem.key?.type === "Identifier" ? mem.key.name : void 0;
6014
+ if (!keyName) continue;
6015
+ const methodNode = mem.type === "MethodDefinition" ? mem.value : mem;
6016
+ if (!methodNode) continue;
6017
+ visitFn(`${className}.${keyName}`, methodNode, paramSet(methodNode));
6018
+ }
6019
+ };
6020
+ const considerDecl = (decl, exported) => {
6021
+ if (!decl) return;
6022
+ if (decl.type === "FunctionDeclaration" && decl.id) {
6023
+ const id = decl.id;
6024
+ visitFn(id.name, decl, paramSet(decl));
6025
+ return;
6026
+ }
6027
+ if (decl.type === "ClassDeclaration" && decl.id?.name) {
6028
+ visitClassMethods(decl.id.name, decl);
6029
+ return;
6030
+ }
6031
+ if (decl.type === "VariableDeclaration") {
6032
+ for (const d of decl.declarations ?? []) {
6033
+ const id = d.id;
6034
+ const init = d.init;
6035
+ if (exported && id?.type === "Identifier" && init && (init.type === "ArrowFunctionExpression" || init.type === "FunctionExpression")) {
6036
+ visitFn(id.name, init, paramSet(init));
6037
+ }
6038
+ }
6039
+ }
6040
+ };
6041
+ const program = ast.program;
6042
+ const bodyStmts = program?.body ?? [];
6043
+ for (const stmt of bodyStmts) {
6044
+ if (stmt.type === "ExportNamedDeclaration") {
6045
+ considerDecl(stmt.declaration, true);
6046
+ for (const spec of stmt.specifiers ?? []) {
6047
+ const local = spec.local;
6048
+ if (local?.type !== "Identifier") continue;
6049
+ for (const s2 of bodyStmts) {
6050
+ if (s2.type === "ClassDeclaration" && s2.id?.name === local.name) {
6051
+ visitClassMethods(local.name, s2);
6052
+ }
6053
+ }
6054
+ }
6055
+ } else if (stmt.type === "ExportDefaultDeclaration") {
6056
+ considerDecl(stmt.declaration, true);
6057
+ } else if (stmt.type === "ClassDeclaration") {
6058
+ const id = stmt.id;
6059
+ if (id?.name) visitClassMethods(id.name, stmt);
6060
+ } else if (stmt.type === "FunctionDeclaration") {
6061
+ }
6062
+ }
6063
+ return out;
6064
+ }
6065
+ function caseEvidence(fn) {
6066
+ const callsite = fn.cases.filter((c) => c.source === "callsite");
6067
+ const directive = fn.cases.filter((c) => c.source === "directive");
6068
+ return {
6069
+ paramCases: callsite.length > 0 ? callsite : directive,
6070
+ returnCases: callsite.length > 0 ? callsite : directive.length > 0 ? directive : fn.cases,
6071
+ paramEvidence: callsite.length > 0 ? "callsite" : directive.length > 0 ? "directive" : "none",
6072
+ rawReturnEvidence: callsite.length > 0 ? "callsite" : directive.length > 0 ? "directive" : "none"
6073
+ };
6074
+ }
6075
+ function widenDraftConstraint(c) {
6076
+ const stripEqLits = (preds2) => preds2.filter((p) => !(p.op === "eq" && p.b?.op === "lit"));
6077
+ if (c.members && c.members.length > 0) {
6078
+ const widenedMembers = c.members.map(widenDraftConstraint).filter((m) => m !== void 0);
6079
+ if (widenedMembers.length === 0) {
6080
+ const prim = litPrimOf(c.members[0]);
6081
+ if (!prim) return void 0;
6082
+ return { __nudoConstraint: true, prim, preds: [] };
6083
+ }
6084
+ const prims = new Set(widenedMembers.map((m) => m.prim).filter(Boolean));
6085
+ if (prims.size === 1 && widenedMembers.every((m) => !m.fields && !m.element && !m.members)) {
6086
+ return { __nudoConstraint: true, prim: [...prims][0], preds: [] };
6087
+ }
6088
+ return {
6089
+ __nudoConstraint: true,
6090
+ ...c.prim ? { prim: c.prim } : {},
6091
+ preds: stripEqLits(c.preds ?? []),
6092
+ members: widenedMembers
6093
+ };
6094
+ }
6095
+ if (c.fields) {
6096
+ const fields = {};
6097
+ for (const [k, f] of Object.entries(c.fields)) {
6098
+ const w = widenDraftConstraint(f.constraint);
6099
+ if (!w) continue;
6100
+ fields[k] = { constraint: w, ...f.optional ? { optional: true } : {} };
6101
+ }
6102
+ return {
6103
+ __nudoConstraint: true,
6104
+ fields,
6105
+ preds: stripEqLits(c.preds ?? [])
6106
+ };
6107
+ }
6108
+ if (c.element) {
6109
+ const el = widenDraftConstraint(c.element);
6110
+ return {
6111
+ __nudoConstraint: true,
6112
+ preds: stripEqLits(c.preds ?? []),
6113
+ ...el ? { element: el } : {}
6114
+ };
6115
+ }
6116
+ if (c.fn) return void 0;
6117
+ const preds = stripEqLits(c.preds ?? []);
6118
+ const hasBounds = preds.length > 0;
6119
+ if (!c.prim && !hasBounds && !isIntFlag(c)) return void 0;
6120
+ return {
6121
+ __nudoConstraint: true,
6122
+ ...c.prim ? { prim: c.prim } : {},
6123
+ preds,
6124
+ // builder 上 .int 是链式方法,truthy 恒真;必须经 isIntFlag
6125
+ ...isIntFlag(c) ? { int: true } : {}
6126
+ };
6127
+ }
6128
+ function litPrimOf(c) {
6129
+ if (!c) return void 0;
6130
+ if (c.prim === "number" || c.prim === "string" || c.prim === "boolean") return c.prim;
6131
+ const eq = c.preds?.find(
6132
+ (p) => p.op === "eq" && p.b?.op === "lit"
6133
+ );
6134
+ const v = eq && eq.b?.op === "lit" ? eq.b.value : void 0;
6135
+ if (typeof v === "number") return "number";
6136
+ if (typeof v === "string") return "string";
6137
+ if (typeof v === "boolean") return "boolean";
6138
+ return c.members?.[0] ? litPrimOf(c.members[0]) : void 0;
6139
+ }
6140
+ function projectDraftParams(fn, paramCases, bodyByParam, formals) {
6141
+ const bodyFor = (name, index) => {
6142
+ if (!bodyByParam) return void 0;
6143
+ const hit = bodyByParam.get(name);
6144
+ if (hit) return hit;
6145
+ const formal = formals?.[index];
6146
+ if (formal && formal.kind === "pattern") {
6147
+ for (const b of formal.bound) {
6148
+ const boundHit = bodyByParam.get(b);
6149
+ if (boundHit) return boundHit;
6150
+ }
6151
+ }
6152
+ return bodyByParam.get(`_p${index}`);
6153
+ };
6154
+ return fn.paramNames.map((name, i) => {
6155
+ const bodyAccesses = bodyFor(name, i) ? [...bodyFor(name, i)].sort() : void 0;
6156
+ const argAbs = [];
6157
+ for (const c of paramCases) {
6158
+ const a = c.argAbs[i];
6159
+ if (a !== void 0) argAbs.push(a);
6160
+ }
6161
+ if (argAbs.length === 0) {
6162
+ if (bodyAccesses && bodyAccesses.length > 0) {
6163
+ return {
6164
+ name,
6165
+ display: `/* body-read { ${bodyAccesses.join(", ")} } \u2014 fill types when accepting */`,
6166
+ projected: false,
6167
+ bodyAccesses
6168
+ };
6169
+ }
6170
+ return { name, display: "/* no evidence \u2014 tighten */", projected: false };
6171
+ }
6172
+ const raw = joinThenProject2(argAbs);
6173
+ if (raw === void 0) {
6174
+ return {
6175
+ name,
6176
+ display: `/* not projectable: ${argAbs.map((a) => formatShape6(a)).join(" | ")} */`,
6177
+ projected: false,
6178
+ ...bodyAccesses ? { bodyAccesses } : {}
6179
+ };
6180
+ }
6181
+ const constraint = widenDraftConstraint(raw);
6182
+ if (constraint === void 0) {
6183
+ const observed2 = formatConstraint3(raw);
6184
+ return {
6185
+ name,
6186
+ display: `/* observed: ${observed2} \u2014 widen/confirm before accepting */`,
6187
+ projected: false,
6188
+ ...bodyAccesses ? { bodyAccesses } : {}
6189
+ };
6190
+ }
6191
+ let display = formatConstraint3(constraint);
6192
+ const observed = formatConstraint3(raw);
6193
+ if (observed !== display) {
6194
+ display += ` /* observed: ${observed} */`;
6195
+ }
6196
+ if (bodyAccesses && bodyAccesses.length > 0) {
6197
+ display += ` /* body also reads: ${bodyAccesses.join(", ")} */`;
6198
+ }
6199
+ return {
6200
+ name,
6201
+ constraint,
6202
+ display,
6203
+ projected: true,
6204
+ ...bodyAccesses ? { bodyAccesses } : {}
6205
+ };
6206
+ });
6207
+ }
6208
+ function projectDraftReturn(fn, returnCases, source, rawEvidence, loadModule, fromFile) {
6209
+ const retAbs = [];
6210
+ for (const c of returnCases) {
6211
+ if (c.throwsAbs.shape.k !== "never") continue;
6212
+ retAbs.push(c.abs);
6213
+ }
6214
+ if (retAbs.length > 0) {
6215
+ const raw = joinThenProject2(retAbs);
6216
+ if (raw !== void 0) {
6217
+ if (rawEvidence === "none" || rawEvidence === "body") {
6218
+ return {
6219
+ display: `/* observed: ${formatConstraint3(raw)} \u2014 confirm before accepting */`,
6220
+ projected: false,
6221
+ evidence: rawEvidence === "none" ? "body" : rawEvidence
6222
+ };
6223
+ }
6224
+ const constraint = widenDraftConstraint(raw);
6225
+ if (constraint === void 0) {
6226
+ return {
6227
+ display: `/* observed: ${formatConstraint3(raw)} \u2014 widen/confirm */`,
6228
+ projected: false,
6229
+ evidence: rawEvidence
6230
+ };
6231
+ }
6232
+ return {
6233
+ constraint,
6234
+ display: formatConstraint3(constraint),
6235
+ projected: true,
6236
+ evidence: rawEvidence
6237
+ };
6238
+ }
6239
+ const shapeText = fn.combinedAbs ? formatShape6(fn.combinedAbs) : formatShape6(retAbs[0]);
6240
+ return {
6241
+ display: `/* not projectable: ${shapeText} */`,
6242
+ projected: false,
6243
+ evidence: rawEvidence
6244
+ };
6245
+ }
6246
+ try {
6247
+ const g = generalizeFromAst4(fn.name, source, {
6248
+ refine: {
6249
+ ...loadModule ? { loadModule } : {},
6250
+ ...fromFile ? { fromFile } : {}
6251
+ }
6252
+ });
6253
+ if (g?.symbolic) {
6254
+ return {
6255
+ display: `/* symbolic: ${formatShape6(g.symbolic)}${g.display ? ` \u2014 ${g.display}` : ""} */`,
6256
+ projected: false,
6257
+ evidence: "symbolic"
6258
+ };
6259
+ }
6260
+ } catch {
6261
+ }
6262
+ return { display: "/* no evidence */", projected: false, evidence: "none" };
6263
+ }
6264
+ function toDraftBuilderDsl(display) {
6265
+ const s = display.replace(/\s*\/\*[\s\S]*?\*\/\s*/g, "").trim();
6266
+ return s.replace(
6267
+ /([A-Za-z_$][\w$]*)\?\s*:\s*([^,}\n]+)/g,
6268
+ (_m, name, val) => {
6269
+ const v = val.trim();
6270
+ if (v.includes(".optional()")) return `${name}: ${v}`;
6271
+ return `${name}: ${v}.optional()`;
6272
+ }
6273
+ );
6274
+ }
6275
+ function draftExportName(fnName) {
6276
+ return fnName.includes(".") ? fnName.replace(/\./g, "_") : fnName;
6277
+ }
6278
+ function draftDsl(entry) {
6279
+ const parts = entry.params.filter((p) => p.projected && p.constraint !== void 0).map((p) => {
6280
+ const pure = toDraftBuilderDsl(formatConstraint3(p.constraint));
6281
+ return `${p.name}: ${pure}`;
6282
+ });
6283
+ const obj = parts.length === 0 ? "{}" : `{ ${parts.join(", ")} }`;
6284
+ const ret = entry.returns?.projected && entry.returns.constraint !== void 0 ? toDraftBuilderDsl(formatConstraint3(entry.returns.constraint)) : void 0;
6285
+ return ret === void 0 || ret === "" ? `fn(${obj})` : `fn(${obj}, ${ret})`;
6286
+ }
6287
+ function suggestedBodyDsl(fnName, params) {
6288
+ const withBody = params.filter((p) => p.bodyAccesses && p.bodyAccesses.length > 0 && !p.projected);
6289
+ if (withBody.length === 0) return void 0;
6290
+ const parts = withBody.map((p) => {
6291
+ const fields = p.bodyAccesses.map((k) => `${k}: /* TODO */`).join(", ");
6292
+ return `${p.name}: shape({ ${fields} })`;
6293
+ });
6294
+ return `// suggested (body-read, not a contract): ${fnName} = fn({ ${parts.join(", ")} })`;
6295
+ }
6296
+ async function draftInterface(filePath, opts = {}) {
6297
+ const source = opts.source ?? readFileSync9(filePath, "utf-8");
6298
+ const loadModule = opts.loadModule ?? defaultLoadModule;
6299
+ const sidecarPath = sidecarPathOf4(filePath);
6300
+ const wantBody = opts.bodyAccesses !== false;
6301
+ const analysis = await analyzeFileAsync(filePath, source, void 0, opts.records, loadModule);
6302
+ const exported = localNamedExports3(source);
6303
+ const selected = opts.fnNames && opts.fnNames.length > 0 ? new Set(opts.fnNames) : exported;
6304
+ const bodyMap = wantBody ? collectParamBodyAccesses(source) : /* @__PURE__ */ new Map();
6305
+ const entries = [];
6306
+ for (const fn of analysis.functions) {
6307
+ if (!selected.has(fn.name) && !opts.fnNames?.includes(fn.name)) continue;
6308
+ if (!exported.has(fn.name)) {
6309
+ if (opts.fnNames?.includes(fn.name)) {
6310
+ entries.push({
6311
+ fn: fn.name,
6312
+ params: fn.paramNames.map((n) => ({
6313
+ name: n,
6314
+ display: "/* not an export */",
6315
+ projected: false
6316
+ })),
6317
+ paramEvidence: "none",
6318
+ returnEvidence: "none",
6319
+ skipped: "not-an-export"
6320
+ });
6321
+ }
6322
+ continue;
6323
+ }
6324
+ const eff = effectiveInterface2(source, fn.name, {
6325
+ loadModule,
6326
+ fromFile: filePath,
6327
+ autoBind: true
6328
+ });
6329
+ if (eff?.source === "handwritten") {
6330
+ entries.push({
6331
+ fn: fn.name,
6332
+ params: eff.params.map((p) => ({
6333
+ name: p.param,
6334
+ constraint: p.constraint,
6335
+ display: formatConstraint3(p.constraint),
6336
+ projected: true
6337
+ })),
6338
+ ...eff.returns ? {
6339
+ returns: {
6340
+ constraint: eff.returns.constraint,
6341
+ display: formatConstraint3(eff.returns.constraint),
6342
+ projected: true
6343
+ }
6344
+ } : {},
6345
+ paramEvidence: "none",
6346
+ returnEvidence: "none",
6347
+ skipped: "handwritten"
6348
+ });
6349
+ continue;
6350
+ }
6351
+ const { paramCases, returnCases, paramEvidence, rawReturnEvidence } = caseEvidence(fn);
6352
+ const bodyByParam = bodyMap.get(fn.name);
6353
+ const params = projectDraftParams(fn, paramCases, bodyByParam, fn.formals);
6354
+ const ret = projectDraftReturn(fn, returnCases, source, rawReturnEvidence, loadModule, filePath);
6355
+ const { evidence: returnEvidence, ...returns } = ret;
6356
+ let evidence = paramEvidence;
6357
+ if (evidence === "none") {
6358
+ const anyBody = params.some((p) => p.bodyAccesses && p.bodyAccesses.length > 0);
6359
+ if (anyBody) evidence = "body";
6360
+ }
6361
+ entries.push({
6362
+ fn: fn.name,
6363
+ params,
6364
+ returns,
6365
+ paramEvidence: evidence,
6366
+ returnEvidence,
6367
+ dsl: draftDsl({ params, returns })
6368
+ });
6369
+ }
6370
+ const draftSource = formatDraftModule(filePath, entries, sidecarPath);
6371
+ return { file: filePath, entries, draftSource, sidecarPath };
6372
+ }
6373
+ function formatDraftModule(filePath, entries, sidecarPath) {
6374
+ const target = sidecarPath ?? sidecarPathOf4(filePath);
6375
+ const draftName = sidecarDraftPath(filePath).split(/[/\\]/).pop() ?? "*.nudo.draft.js";
6376
+ const draftable = entries.filter((e) => e.dsl !== void 0 && e.skipped === void 0);
6377
+ const BUILDERS = ["fn", "number", "string", "boolean", "any", "shape", "array", "lit", "union"];
6378
+ const used = /* @__PURE__ */ new Set(["fn"]);
6379
+ for (const e of draftable) {
6380
+ const blob = `${e.dsl ?? ""}
6381
+ ${suggestedBodyDsl(e.fn, e.params) ?? ""}`;
6382
+ for (const b of BUILDERS) {
6383
+ if (new RegExp(`\\b${b}\\b`).test(blob)) used.add(b);
6384
+ }
6385
+ }
6386
+ const importList = BUILDERS.filter((b) => used.has(b)).join(", ");
6387
+ const lines = [
6388
+ "// @nudo:draft",
6389
+ `// Generated by \`nudo interface --draft\` from ${filePath}`,
6390
+ `// This ${draftName} file is NOT loaded as a sidecar contract.`,
6391
+ `// Review each export, then copy it into ${target} to accept.`,
6392
+ "//",
6393
+ "// Evidence: callsite/directive = observed args; body = fields the",
6394
+ "// implementation reads (suggestion only \u2014 never a check obligation);",
6395
+ "// symbolic = generalize; omitted params = no evidence.",
6396
+ "// Handwritten contracts are never overwritten.",
6397
+ "",
6398
+ `import { ${importList} } from "@nudojs/core";`,
6399
+ ""
6400
+ ];
6401
+ if (draftable.length === 0) {
6402
+ lines.push("// (no draftable exports \u2014 handwritten / non-export / empty)");
6403
+ lines.push("");
6404
+ }
6405
+ for (const e of draftable) {
6406
+ lines.push(`// ${e.fn} \u2014 param: ${e.paramEvidence}, return: ${e.returnEvidence}`);
6407
+ for (const p of e.params) {
6408
+ if (!p.projected) {
6409
+ lines.push(`// ${p.name}: ${p.display}`);
6410
+ } else if (p.display.includes("/* observed") || p.display.includes("/* body also reads")) {
6411
+ lines.push(`// ${p.name}: ${p.display}`);
6412
+ }
6413
+ }
6414
+ const suggested = suggestedBodyDsl(e.fn, e.params);
6415
+ if (suggested) lines.push(suggested);
6416
+ if (e.returns && !e.returns.projected) {
6417
+ lines.push(`// returns: ${e.returns.display}`);
6418
+ } else if (e.returns?.display.includes("/* observed")) {
6419
+ lines.push(`// returns: ${e.returns.display}`);
6420
+ } else if (e.returnEvidence === "symbolic") {
6421
+ lines.push(`// returns: ${e.returns?.display ?? ""} (symbolic)`);
6422
+ }
6423
+ lines.push(`export const ${draftExportName(e.fn)} = ${e.dsl};`);
6424
+ if (e.fn.includes(".")) {
6425
+ lines.push(`// sidecar key may also be written as \`${e.fn}\` / nested { ${e.fn.split(".")[1]}: \u2026 }`);
6426
+ }
6427
+ lines.push("");
6428
+ }
6429
+ const skipped = entries.filter((e) => e.skipped !== void 0);
6430
+ if (skipped.length > 0) {
6431
+ lines.push("// Skipped:");
6432
+ for (const s of skipped) {
6433
+ lines.push(`// ${s.fn} (${s.skipped})`);
6434
+ }
6435
+ lines.push("");
6436
+ }
6437
+ return lines.join("\n");
6438
+ }
6439
+ function sidecarDraftPath(filePath) {
6440
+ return sidecarPathOf4(filePath).replace(/\.nudo\.([cm]?[jt]s)$/, ".nudo.draft.$1");
6441
+ }
6442
+ function safeRealpath(p) {
6443
+ try {
6444
+ return realpathSync2(p);
6445
+ } catch {
6446
+ try {
6447
+ return join6(realpathSync2(dirname13(p)), basename3(p));
6448
+ } catch {
6449
+ return p;
6450
+ }
6451
+ }
6452
+ }
6453
+ function isDraftableEntry(entries) {
6454
+ return entries.some((e) => e.dsl !== void 0 && e.skipped === void 0);
6455
+ }
6456
+ function writeInterfaceDraft(filePath, draftSource, opts = {}) {
6457
+ const draftPath = sidecarDraftPath(filePath);
6458
+ const formalPath = sidecarPathOf4(filePath);
6459
+ if (draftPath === formalPath) {
6460
+ throw new Error(
6461
+ `draft write refused: draft path equals formal sidecar (${formalPath}); never overwrite handwritten contracts`
6462
+ );
6463
+ }
6464
+ if (/[/\\]node_modules[/\\]/.test(draftPath) || /[/\\]node_modules[/\\]/.test(filePath)) {
6465
+ throw new Error(`draft write refused: path is inside node_modules (${draftPath})`);
6466
+ }
6467
+ if (opts.projectDir) {
6468
+ const rootReal = safeRealpath(opts.projectDir);
6469
+ const draftReal = safeRealpath(draftPath);
6470
+ const rel = relative3(rootReal, draftReal);
6471
+ if (rel.startsWith("..") || isAbsolute2(rel)) {
6472
+ throw new Error(`draft write refused: outside project root ${opts.projectDir}`);
6473
+ }
6474
+ }
6475
+ const draftable = opts.draftable !== void 0 ? opts.draftable : opts.entries !== void 0 ? isDraftableEntry(opts.entries) : /export\s+const\s+[\p{ID_Start}$_][\p{ID_Continue}$]*\s*=/u.test(draftSource);
6476
+ const prev = existsSync11(draftPath) ? readFileSync9(draftPath, "utf-8") : void 0;
6477
+ const changed = prev !== draftSource;
6478
+ const written = !opts.dryRun && changed && draftable;
6479
+ if (written) {
6480
+ writeFileSync3(draftPath, draftSource, "utf-8");
6481
+ }
6482
+ return {
6483
+ draftPath,
6484
+ written,
6485
+ changed,
6486
+ draftable,
6487
+ draftSource
6488
+ };
6489
+ }
6490
+ function formatDraftSummary(sourceRel, draftRel, result, write) {
6491
+ const lines = [sourceRel];
6492
+ for (const e of result.entries) {
6493
+ if (e.skipped === "handwritten") {
6494
+ lines.push(` ${e.fn} [handwritten] skipped (draft never overwrites)`);
6495
+ } else if (e.skipped === "not-an-export") {
6496
+ lines.push(` ${e.fn} [not-an-export] skipped`);
6497
+ } else if (e.dsl) {
6498
+ lines.push(` ${e.fn} [draft ${e.paramEvidence}/${e.returnEvidence}] ${e.dsl}`);
6499
+ }
6500
+ }
6501
+ if (write) {
6502
+ if (write.changed && write.draftable) {
6503
+ lines.push(
6504
+ write.written ? `Draft written \u2192 ${draftRel}` : `[dry-run] would write \u2192 ${draftRel}`
6505
+ );
6506
+ } else if (write.changed && !write.draftable) {
6507
+ if (result.entries.length > 0) {
6508
+ const skipped = result.entries.filter((e) => e.skipped !== void 0).length;
6509
+ lines.push(
6510
+ skipped > 0 ? `Draft not written (${skipped} skipped, no writeable exports); nothing written \u2192 ${draftRel}` : `Draft not written (no writeable exports); nothing written \u2192 ${draftRel}`
6511
+ );
6512
+ } else {
6513
+ lines.push(`Draft empty (no draftable exports); nothing written \u2192 ${draftRel}`);
6514
+ }
6515
+ } else {
6516
+ lines.push(`${draftRel}: draft unchanged`);
6517
+ }
6518
+ lines.push(` review, then copy accepted exports into ${sidecarPathOf4(result.file)}`);
6519
+ } else {
6520
+ lines.push("");
6521
+ lines.push(result.draftSource.trimEnd());
6522
+ }
6523
+ return lines;
6524
+ }
6525
+
6526
+ // src/interface-derivation.ts
6527
+ import { existsSync as existsSync12, readFileSync as readFileSync10, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
6528
+ import { basename as basename4, dirname as dirname14, relative as relative4, resolve as resolve9 } from "path";
6529
+ import { parse as parse10 } from "@nudojs/parser";
4979
6530
  import {
4980
6531
  analyzeFn,
4981
6532
  beginDerivationSession,
4982
6533
  constraintToEntryAbs,
4983
6534
  derivationChain,
4984
- effectiveInterface as effectiveInterface2,
6535
+ effectiveInterface as effectiveInterface3,
4985
6536
  endDerivationSession,
4986
6537
  execNudoModule as execNudoModule2,
4987
- formatConstraint as formatConstraint3,
6538
+ formatConstraint as formatConstraint4,
4988
6539
  getDerivation,
4989
6540
  interfaceDiagCount as interfaceDiagCount3,
4990
6541
  isNodeModulesPath as isNodeModulesPath2,
4991
6542
  isNudoConstraint as isNudoConstraint2,
4992
- joinThenProject as joinThenProject2,
6543
+ joinThenProject as joinThenProject3,
4993
6544
  parseSource as parseSource3,
4994
6545
  projectDerivationDsl,
4995
6546
  refineDiagCount as refineDiagCount3,
4996
6547
  setAbsCallCollector as setAbsCallCollector2,
4997
- sidecarPathOf as sidecarPathOf2,
6548
+ sidecarPathOf as sidecarPathOf5,
4998
6549
  tagDerivationRoot,
4999
6550
  takeInterfaceDiagsSince as takeInterfaceDiagsSince3,
5000
6551
  takeRefineDiagsSince as takeRefineDiagsSince3,
@@ -5004,7 +6555,7 @@ function extractFnConstraintSources(sidecarSrc, fnName) {
5004
6555
  const out = { params: {} };
5005
6556
  let ast;
5006
6557
  try {
5007
- ast = parse9(sidecarSrc);
6558
+ ast = parse10(sidecarSrc);
5008
6559
  } catch {
5009
6560
  return out;
5010
6561
  }
@@ -5075,7 +6626,7 @@ function extractFnConstraintSources(sidecarSrc, fnName) {
5075
6626
  }
5076
6627
  function functionParamNames(source, fnName) {
5077
6628
  try {
5078
- const ast = parse9(source);
6629
+ const ast = parse10(source);
5079
6630
  for (const stmt of ast.program.body) {
5080
6631
  const d = stmt.type === "ExportNamedDeclaration" ? stmt.declaration ?? void 0 : stmt;
5081
6632
  if (!d) continue;
@@ -5104,7 +6655,7 @@ function paramNameOf(p) {
5104
6655
  function topLevelFnNames(source) {
5105
6656
  const out = [];
5106
6657
  try {
5107
- const ast = parse9(source);
6658
+ const ast = parse10(source);
5108
6659
  for (const stmt of ast.program.body) {
5109
6660
  const d = stmt.type === "ExportNamedDeclaration" ? stmt.declaration ?? void 0 : stmt;
5110
6661
  if (!d) continue;
@@ -5124,16 +6675,16 @@ function topLevelFnNames(source) {
5124
6675
  function importLocalMap(source, fromFile) {
5125
6676
  const out = /* @__PURE__ */ new Map();
5126
6677
  try {
5127
- const ast = parse9(source);
5128
- const base = dirname10(resolve8(fromFile));
6678
+ const ast = parse10(source);
6679
+ const base = dirname14(resolve9(fromFile));
5129
6680
  for (const stmt of ast.program.body) {
5130
6681
  if (stmt.type !== "ImportDeclaration") continue;
5131
6682
  const spec = stmt.source.value;
5132
6683
  if (!spec.startsWith(".") && !spec.startsWith("/")) continue;
5133
- const raw = resolve8(base, spec);
6684
+ const raw = resolve9(base, spec);
5134
6685
  let modulePath = null;
5135
6686
  for (const cand of [raw, `${raw}.js`, `${raw}.ts`, `${raw}.mjs`]) {
5136
- if (existsSync7(cand)) {
6687
+ if (existsSync12(cand)) {
5137
6688
  modulePath = cand;
5138
6689
  break;
5139
6690
  }
@@ -5151,14 +6702,14 @@ function importLocalMap(source, fromFile) {
5151
6702
  return out;
5152
6703
  }
5153
6704
  function resolveRelImport(fromSpec, fromDir, targetDir) {
5154
- const abs = resolve8(fromDir, fromSpec);
5155
- let rel = relative2(targetDir, abs);
6705
+ const abs = resolve9(fromDir, fromSpec);
6706
+ let rel = relative4(targetDir, abs);
5156
6707
  if (!rel.startsWith(".")) rel = `./${rel}`;
5157
6708
  return rel.split("\\").join("/");
5158
6709
  }
5159
6710
  function projectParamSlot(absList, paramName) {
5160
6711
  if (absList.length === 0) return void 0;
5161
- const constraint = joinThenProject2(absList);
6712
+ const constraint = joinThenProject3(absList);
5162
6713
  if (constraint === void 0) return void 0;
5163
6714
  if (absList.length === 1) {
5164
6715
  const node = getDerivation(absList[0]);
@@ -5182,7 +6733,7 @@ function projectParamSlot(absList, paramName) {
5182
6733
  }
5183
6734
  return {
5184
6735
  constraint,
5185
- dsl: formatConstraint3(constraint),
6736
+ dsl: formatConstraint4(constraint),
5186
6737
  prelude: [],
5187
6738
  imports: [],
5188
6739
  compositional: false
@@ -5190,7 +6741,7 @@ function projectParamSlot(absList, paramName) {
5190
6741
  }
5191
6742
  function projectReturnSlot(retAbs, params, paramNames) {
5192
6743
  if (retAbs.length === 0) return void 0;
5193
- const constraint = joinThenProject2(retAbs);
6744
+ const constraint = joinThenProject3(retAbs);
5194
6745
  if (constraint === void 0) return void 0;
5195
6746
  if (retAbs.length === 1) {
5196
6747
  const node = getDerivation(retAbs[0]);
@@ -5219,7 +6770,7 @@ function projectReturnSlot(retAbs, params, paramNames) {
5219
6770
  }
5220
6771
  return {
5221
6772
  constraint,
5222
- dsl: formatConstraint3(constraint),
6773
+ dsl: formatConstraint4(constraint),
5223
6774
  prelude: [],
5224
6775
  imports: [],
5225
6776
  compositional: false
@@ -5251,18 +6802,18 @@ function projectReturnRelativeToParams(node, params, paramNames) {
5251
6802
  return void 0;
5252
6803
  }
5253
6804
  function deriveFromRoot(filePath, opts = {}) {
5254
- const abs = resolve8(filePath);
5255
- const source = readFileSync8(abs, "utf-8");
6805
+ const abs = resolve9(filePath);
6806
+ const source = readFileSync10(abs, "utf-8");
5256
6807
  const since = interfaceDiagCount3();
5257
- const autoBind = opts.autoBind ?? interfaceConfig(findProjectConfig(dirname10(abs))?.config).autoBind;
6808
+ const autoBind = opts.autoBind ?? interfaceConfig(findProjectConfig(dirname14(abs))?.config).autoBind;
5258
6809
  const loadModule = opts.loadModule ?? defaultLoadModule;
5259
- const sidecarPath = sidecarPathOf2(abs);
5260
- const sidecarSrc = existsSync7(sidecarPath) && !isNodeModulesPath2(sidecarPath) ? readFileSync8(sidecarPath, "utf-8") : "";
6810
+ const sidecarPath = sidecarPathOf5(abs);
6811
+ const sidecarSrc = existsSync12(sidecarPath) && !isNodeModulesPath2(sidecarPath) ? readFileSync10(sidecarPath, "utf-8") : "";
5261
6812
  const fnNames = topLevelFnNames(source);
5262
6813
  const roots = [];
5263
6814
  const plans = [];
5264
6815
  for (const fn of fnNames) {
5265
- const eff = effectiveInterface2(source, fn, { loadModule, fromFile: abs, autoBind });
6816
+ const eff = effectiveInterface3(source, fn, { loadModule, fromFile: abs, autoBind });
5266
6817
  if (!eff || eff.source !== "handwritten") continue;
5267
6818
  roots.push(fn);
5268
6819
  const sources = sidecarSrc ? extractFnConstraintSources(sidecarSrc, fn) : { params: {} };
@@ -5298,8 +6849,8 @@ function deriveFromRoot(filePath, opts = {}) {
5298
6849
  const wanted = opts.fnNames && opts.fnNames.length > 0 ? new Set(opts.fnNames) : void 0;
5299
6850
  const derived = [];
5300
6851
  for (const plan of plans) {
5301
- const relRoot = relative2(process.cwd(), abs);
5302
- const label = `${relRoot === "" || relRoot.startsWith("..") ? basename3(abs) : relRoot}:${plan.fnName}`;
6852
+ const relRoot = relative4(process.cwd(), abs);
6853
+ const label = `${relRoot === "" || relRoot.startsWith("..") ? basename4(abs) : relRoot}:${plan.fnName}`;
5303
6854
  const rows = deriveOneRoot(plan, source, abs, modules, importLocals, label);
5304
6855
  for (const row of rows) {
5305
6856
  if (wanted && !wanted.has(row.fn)) continue;
@@ -5346,7 +6897,7 @@ function deriveOneRoot(plan, source, file, modules, importLocals, label) {
5346
6897
  targetFile = file;
5347
6898
  }
5348
6899
  if (isNodeModulesPath2(targetFile)) continue;
5349
- if (!existsSync7(targetFile)) continue;
6900
+ if (!existsSync12(targetFile)) continue;
5350
6901
  const key = `${targetFile}::${targetExport}`;
5351
6902
  let agg = byCallee.get(key);
5352
6903
  if (!agg) {
@@ -5360,7 +6911,7 @@ function deriveOneRoot(plan, source, file, modules, importLocals, label) {
5360
6911
  for (const agg of byCallee.values()) {
5361
6912
  let targetSource;
5362
6913
  try {
5363
- targetSource = readFileSync8(agg.targetFile, "utf-8");
6914
+ targetSource = readFileSync10(agg.targetFile, "utf-8");
5364
6915
  } catch {
5365
6916
  continue;
5366
6917
  }
@@ -5597,7 +7148,7 @@ function sidecarAssembles(text, fromFile, loadModule) {
5597
7148
  }
5598
7149
  }
5599
7150
  function emitDerivedFromRoot(rootFile, opts) {
5600
- const abs = resolve8(rootFile);
7151
+ const abs = resolve9(rootFile);
5601
7152
  const derive = deriveFromRoot(abs, {
5602
7153
  ...opts.loadModule ? { loadModule: opts.loadModule } : {},
5603
7154
  ...opts.autoBind !== void 0 ? { autoBind: opts.autoBind } : {},
@@ -5606,10 +7157,10 @@ function emitDerivedFromRoot(rootFile, opts) {
5606
7157
  if (!derive.hasRoot) {
5607
7158
  return { sidecars: [], hasRoot: false, roots: [], entryOnly: true };
5608
7159
  }
5609
- const proj = findProjectConfig(dirname10(abs));
7160
+ const proj = findProjectConfig(dirname14(abs));
5610
7161
  const allow = interfaceConfig(proj?.config).emit;
5611
7162
  const projectDir = proj?.projectDir;
5612
- const rootSidecarDir = dirname10(sidecarPathOf2(abs));
7163
+ const rootSidecarDir = dirname14(sidecarPathOf5(abs));
5613
7164
  const wanted = opts.fnNames && opts.fnNames.length > 0 ? new Set(opts.fnNames) : void 0;
5614
7165
  const result = {
5615
7166
  sidecars: [],
@@ -5620,7 +7171,7 @@ function emitDerivedFromRoot(rootFile, opts) {
5620
7171
  for (const row of derive.derived) {
5621
7172
  if (wanted && !wanted.has(row.fn)) continue;
5622
7173
  if (!matchesEmitAllowlist(row.file, projectDir, allow)) continue;
5623
- const sp = sidecarPathOf2(row.file);
7174
+ const sp = sidecarPathOf5(row.file);
5624
7175
  if (isNodeModulesPath2(sp)) continue;
5625
7176
  const list = bySidecar.get(sp) ?? [];
5626
7177
  list.push(row);
@@ -5628,8 +7179,8 @@ function emitDerivedFromRoot(rootFile, opts) {
5628
7179
  }
5629
7180
  for (const [sidecarPath, rows0] of bySidecar) {
5630
7181
  const targetFile = rows0[0].file;
5631
- const targetSidecarDir = dirname10(sidecarPath);
5632
- const prevSrc = existsSync7(sidecarPath) ? readFileSync8(sidecarPath, "utf-8") : "";
7182
+ const targetSidecarDir = dirname14(sidecarPath);
7183
+ const prevSrc = existsSync12(sidecarPath) ? readFileSync10(sidecarPath, "utf-8") : "";
5633
7184
  const rows = opts.refreshExistingOnly ? rows0.filter((r) => findGeneratedSectionText(prevSrc, r.fn) !== void 0) : rows0;
5634
7185
  if (rows.length === 0) continue;
5635
7186
  const handwritten = handwrittenNames(prevSrc);
@@ -5648,7 +7199,7 @@ function emitDerivedFromRoot(rootFile, opts) {
5648
7199
  issues.push({
5649
7200
  code: "nudo:interface-name-clash",
5650
7201
  severity: "error",
5651
- message: `sidecar already has a handwritten binding '${row.fn}' (${relative2(process.cwd(), sidecarPath) || sidecarPath}); handwritten wins \u2014 skipping emit`
7202
+ message: `sidecar already has a handwritten binding '${row.fn}' (${relative4(process.cwd(), sidecarPath) || sidecarPath}); handwritten wins \u2014 skipping emit`
5652
7203
  });
5653
7204
  anySkip = "name-clash";
5654
7205
  continue;
@@ -5676,7 +7227,7 @@ function emitDerivedFromRoot(rootFile, opts) {
5676
7227
  continue;
5677
7228
  }
5678
7229
  for (const n of body.usedNames) taken.add(n);
5679
- const srcRel = relative2(targetSidecarDir, targetFile) || basename3(targetFile);
7230
+ const srcRel = relative4(targetSidecarDir, targetFile) || basename4(targetFile);
5680
7231
  const section = [
5681
7232
  DERIVED_HEADER,
5682
7233
  `// source: ${srcRel}:${row.fn}`,
@@ -5696,8 +7247,8 @@ function emitDerivedFromRoot(rootFile, opts) {
5696
7247
  for (const n of collectTopLevelNames(prevSection)) taken.add(n);
5697
7248
  continue;
5698
7249
  }
5699
- const norm = normalizeSectionText(section);
5700
- if (prevSection !== void 0 && normalizeSectionText(prevSection) === norm) {
7250
+ const norm2 = normalizeSectionText(section);
7251
+ if (prevSection !== void 0 && normalizeSectionText(prevSection) === norm2) {
5701
7252
  anySkip = "no-change";
5702
7253
  accepted.push({ fn: row.fn, text: prevSection, prevText: prevSection });
5703
7254
  for (const n of collectTopLevelNames(prevSection)) taken.add(n);
@@ -5726,7 +7277,7 @@ function emitDerivedFromRoot(rootFile, opts) {
5726
7277
  issues.push({
5727
7278
  code: "nudo:interface-not-projectable",
5728
7279
  severity: "error",
5729
- message: `assembled sidecar failed round-trip (${relative2(process.cwd(), sidecarPath) || sidecarPath}); refusing to write`
7280
+ message: `assembled sidecar failed round-trip (${relative4(process.cwd(), sidecarPath) || sidecarPath}); refusing to write`
5730
7281
  });
5731
7282
  anySkip = "not-projectable";
5732
7283
  result.sidecars.push({
@@ -5741,15 +7292,15 @@ function emitDerivedFromRoot(rootFile, opts) {
5741
7292
  continue;
5742
7293
  }
5743
7294
  const changed = finalContent !== prevSrc;
5744
- const diff = changed ? unifiedDiff(prevSrc, finalContent, relative2(process.cwd(), sidecarPath) || sidecarPath) : void 0;
7295
+ const diff = changed ? unifiedDiff(prevSrc, finalContent, relative4(process.cwd(), sidecarPath) || sidecarPath) : void 0;
5745
7296
  if (changed && !opts.dryRun) {
5746
7297
  const tmp = `${sidecarPath}.tmp-${process.pid}-${Math.random().toString(16).slice(2, 10)}`;
5747
7298
  try {
5748
- writeFileSync2(tmp, finalContent, "utf-8");
7299
+ writeFileSync4(tmp, finalContent, "utf-8");
5749
7300
  renameSync2(tmp, sidecarPath);
5750
7301
  } catch (e) {
5751
7302
  try {
5752
- if (existsSync7(tmp)) unlinkSync2(tmp);
7303
+ if (existsSync12(tmp)) unlinkSync2(tmp);
5753
7304
  } catch {
5754
7305
  }
5755
7306
  throw e;
@@ -5900,10 +7451,16 @@ function derivedRoundTrips(text, fn, fromFile, loadModule) {
5900
7451
  }
5901
7452
  }
5902
7453
  export {
7454
+ ANALYSIS_ABI,
7455
+ DEFAULT_ANALYSIS_MODE,
7456
+ DiskCache,
5903
7457
  SEMANTIC_TOKEN_MODIFIERS,
5904
7458
  SEMANTIC_TOKEN_TYPES,
5905
7459
  absToTSType,
5906
7460
  absToZodSchema,
7461
+ ambientSourcesOfSidecar,
7462
+ analysisConfig,
7463
+ analysisFileCacheKey,
5907
7464
  analyzeExportsFromSource,
5908
7465
  analyzeFile,
5909
7466
  analyzeFileAsync,
@@ -5913,12 +7470,15 @@ export {
5913
7470
  buildCaseDirective,
5914
7471
  buildModuleGraph,
5915
7472
  buildSemanticTokens,
7473
+ checkCacheKey,
5916
7474
  clearAbsModuleCache,
5917
7475
  clearAnalysisFileCache,
5918
7476
  clearAnalysisSessionCaches,
5919
7477
  clearBPathCache,
7478
+ clearEnvPathDeps,
5920
7479
  clearFnAnalysisCache,
5921
7480
  clearHarvestCache,
7481
+ clearPathEnvCaches,
5922
7482
  collectAbsBindingsFromGraph,
5923
7483
  collectAbsInlays,
5924
7484
  collectBPathDiagnostics,
@@ -5929,14 +7489,20 @@ export {
5929
7489
  collectDtsFromEntry,
5930
7490
  collectEnvGlobals,
5931
7491
  collectEnvModules,
7492
+ collectLoadDepContents,
7493
+ collectParamBodyAccesses,
5932
7494
  collectStaticImports,
5933
7495
  computeDirtySet,
5934
7496
  defaultAbsLoadModule,
5935
7497
  defaultLoadModule,
5936
7498
  deriveFromRoot,
7499
+ diagnosticsLevelForFile,
7500
+ diskCacheRoot,
7501
+ draftInterface,
5937
7502
  emitDerivedFromRoot,
5938
7503
  emitInterface,
5939
7504
  encodeSemanticTokens,
7505
+ envPathDependents,
5940
7506
  evalAbsModuleGraph,
5941
7507
  evalProgramAbsWithModules,
5942
7508
  evictAbsModuleCacheFiles,
@@ -5945,8 +7511,12 @@ export {
5945
7511
  evictBPathCacheForFiles,
5946
7512
  evictFnAnalysisCacheForFiles,
5947
7513
  extractFnConstraintSources,
7514
+ extractNudoImportSpecs,
7515
+ filterDiagnosticsByLevel,
5948
7516
  findProjectConfig,
5949
7517
  formatDerivedSection,
7518
+ formatDraftModule,
7519
+ formatDraftSummary,
5950
7520
  formatEmitSummary,
5951
7521
  formatHarvestSummary,
5952
7522
  formatInterfaceSurfaceLine,
@@ -5957,6 +7527,7 @@ export {
5957
7527
  getAbsAtPosition,
5958
7528
  getAbsAtPositionAsync,
5959
7529
  getAnalysisFileCacheSize,
7530
+ getAnalysisSession,
5960
7531
  getCasesForFile,
5961
7532
  getCompletionsAtPosition,
5962
7533
  getHoverAtPosition,
@@ -5967,24 +7538,39 @@ export {
5967
7538
  harvestPackageCached,
5968
7539
  harvestToAbsModules,
5969
7540
  harvestedValueToAbs,
7541
+ ifaceCacheKey,
5970
7542
  insertGeneratedCaseDirectives,
5971
7543
  interfaceConfig,
5972
7544
  interfaceSurface,
7545
+ interfaceTierModifierBit,
5973
7546
  isBPathCapable,
7547
+ isDraftableEntry,
7548
+ isEnvTemplatePath,
5974
7549
  isNudoTargetPath,
7550
+ isProjectConfigPath,
7551
+ isSidecarPath,
7552
+ isWatchRelevantPath,
5975
7553
  lookupHarvested,
5976
7554
  matchesEmitAllowlist,
5977
7555
  mockDirectivesToAbsSeeds,
7556
+ noteEnvPathDeps,
5978
7557
  packageHarvestToAbsModules,
7558
+ relativizePath,
5979
7559
  resetAllAnalysisCaches,
5980
7560
  resolvePackageRoot,
5981
7561
  serializeCaseArg,
5982
7562
  serializeInferJson,
7563
+ setAnalysisSession,
7564
+ sha256Hex,
7565
+ shouldAnalyzeFile,
7566
+ sidecarDraftPath,
7567
+ hasNudoDirectives as sourceHasNudoDirectives,
5983
7568
  stripGeneratedCaseDirectives,
5984
7569
  summarizeNodeEnv,
5985
7570
  topoSortDirty,
5986
7571
  tryBPathCall,
5987
7572
  tryBPathCallFull,
5988
7573
  tryRunBPath,
5989
- unifiedDiff
7574
+ unifiedDiff,
7575
+ writeInterfaceDraft
5990
7576
  };