nudojs 1.2.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +1671 -581
  2. package/package.json +6 -6
package/dist/index.js CHANGED
@@ -59,10 +59,14 @@ function termEquals(a, b) {
59
59
  }
60
60
  return false;
61
61
  }
62
+ function litKeyString(v2) {
63
+ if (typeof v2 === "number" && Object.is(v2, -0)) return "-0";
64
+ return String(v2);
65
+ }
62
66
  function termToString(t) {
63
67
  if (t.op === "lit") {
64
68
  if (typeof t.value === "string") return JSON.stringify(t.value);
65
- if (typeof t.value === "number" && Object.is(t.value, -0)) return "-0";
69
+ if (typeof t.value === "number") return litKeyString(t.value);
66
70
  return String(t.value);
67
71
  }
68
72
  if (t.op === "var") return t.id;
@@ -1012,6 +1016,48 @@ var init_pred = __esm({
1012
1016
  }
1013
1017
  });
1014
1018
 
1019
+ // ../core/src/algebra/projection-budget.ts
1020
+ var PROJECTION_MAX_DEPTH, ProjectionBudget;
1021
+ var init_projection_budget = __esm({
1022
+ "../core/src/algebra/projection-budget.ts"() {
1023
+ "use strict";
1024
+ PROJECTION_MAX_DEPTH = 64;
1025
+ ProjectionBudget = class {
1026
+ #maxDepth;
1027
+ #path = [];
1028
+ #onPath = /* @__PURE__ */ new Set();
1029
+ /** 粘性:本次投影是否发生过截断("cycle" / "depth"),供台账记录 */
1030
+ truncated;
1031
+ constructor(maxDepth = PROJECTION_MAX_DEPTH) {
1032
+ this.#maxDepth = maxDepth;
1033
+ }
1034
+ /**
1035
+ * 进入一个 Abs(或 Shape)子树。返回 null → 继续递归,之后必须配对
1036
+ * `exit()`;返回 "cycle" | "depth" → 不得递归,按出口各自的约定渲染
1037
+ * 截断标记(不调用 exit——本次 enter 没有入栈)。
1038
+ */
1039
+ enter(a) {
1040
+ if (this.#onPath.has(a)) {
1041
+ this.truncated = "cycle";
1042
+ return "cycle";
1043
+ }
1044
+ if (this.#path.length >= this.#maxDepth) {
1045
+ this.truncated = "depth";
1046
+ return "depth";
1047
+ }
1048
+ this.#path.push(a);
1049
+ this.#onPath.add(a);
1050
+ return null;
1051
+ }
1052
+ /** 离开子树(与返回 null 的 enter 严格配对)。 */
1053
+ exit() {
1054
+ const a = this.#path.pop();
1055
+ if (a !== void 0) this.#onPath.delete(a);
1056
+ }
1057
+ };
1058
+ }
1059
+ });
1060
+
1015
1061
  // ../core/src/algebra/abs.ts
1016
1062
  function anyVar(id, conf = "path") {
1017
1063
  return { shape: { k: "any" }, term: { op: "var", id }, conf };
@@ -1075,13 +1121,34 @@ function confJoin(a, b) {
1075
1121
  return rank[Math.max(ia < 0 ? 5 : ia, ib < 0 ? 5 : ib)];
1076
1122
  }
1077
1123
  function absToString(a) {
1078
- const parts = [shapeToString(a.shape)];
1079
- if (a.term) parts.push(`term=${termToString(a.term)}`);
1080
- if (a.pred && a.pred.op !== "true") parts.push(predToString(a.pred));
1081
- parts.push(`#${a.conf}`);
1082
- return parts.join(" ");
1124
+ return absToStringB(a, new ProjectionBudget());
1125
+ }
1126
+ function absToStringB(a, budget) {
1127
+ const stop2 = budget.enter(a);
1128
+ if (stop2) return stop2 === "cycle" ? "\u2026cycle" : "\u2026";
1129
+ try {
1130
+ const parts = [shapeToStringB(a.shape, budget)];
1131
+ if (a.term) parts.push(`term=${termToString(a.term)}`);
1132
+ if (a.pred && a.pred.op !== "true") parts.push(predToString(a.pred));
1133
+ parts.push(`#${a.conf}`);
1134
+ return parts.join(" ");
1135
+ } finally {
1136
+ budget.exit();
1137
+ }
1083
1138
  }
1084
1139
  function shapeToString(s) {
1140
+ return shapeToStringB(s, new ProjectionBudget());
1141
+ }
1142
+ function shapeToStringB(s, budget) {
1143
+ const stop2 = budget.enter(s);
1144
+ if (stop2) return stop2 === "cycle" ? "\u2026cycle" : "\u2026";
1145
+ try {
1146
+ return shapeToStringInner(s, budget);
1147
+ } finally {
1148
+ budget.exit();
1149
+ }
1150
+ }
1151
+ function shapeToStringInner(s, budget) {
1085
1152
  switch (s.k) {
1086
1153
  case "never":
1087
1154
  return "never";
@@ -1093,22 +1160,22 @@ function shapeToString(s) {
1093
1160
  return s.type;
1094
1161
  case "obj": {
1095
1162
  const entries = Object.entries(s.slots).map(
1096
- ([k, slot]) => `${k}${slot.optional ? "?" : ""}: ${absToString(slot.value)}`
1163
+ ([k, slot]) => `${k}${slot.optional ? "?" : ""}: ${absToStringB(slot.value, budget)}`
1097
1164
  );
1098
1165
  return `{ ${entries.join(", ")} }`;
1099
1166
  }
1100
1167
  case "arr":
1101
- return `${shapeToString(s.element.shape)}[]`;
1168
+ return `${shapeToStringB(s.element.shape, budget)}[]`;
1102
1169
  case "tuple":
1103
- return `[${s.elements.map((e) => shapeToString(e.shape)).join(", ")}]`;
1170
+ return `[${s.elements.map((e) => shapeToStringB(e.shape, budget)).join(", ")}]`;
1104
1171
  case "fn":
1105
1172
  return `fn(${s.params.join(", ")})${s.name ? ` ${s.name}` : ""}`;
1106
1173
  case "brand":
1107
1174
  return s.name;
1108
1175
  case "eff":
1109
- return `${s.eff}<${shapeToString(s.inner.shape)}>`;
1176
+ return `${s.eff}<${shapeToStringB(s.inner.shape, budget)}>`;
1110
1177
  case "sum":
1111
- return s.members.map(absToString).join(" | ");
1178
+ return s.members.map((m) => absToStringB(m, budget)).join(" | ");
1112
1179
  default:
1113
1180
  return "\xB7";
1114
1181
  }
@@ -1153,6 +1220,7 @@ var init_abs = __esm({
1153
1220
  "use strict";
1154
1221
  init_term();
1155
1222
  init_pred();
1223
+ init_projection_budget();
1156
1224
  never = { shape: { k: "never" }, conf: "exact" };
1157
1225
  unknown = { shape: { k: "unknown" }, conf: "partial" };
1158
1226
  anyAbs = { shape: { k: "any" }, conf: "path" };
@@ -15788,7 +15856,7 @@ function absFunction(params, impl, opts) {
15788
15856
  return a;
15789
15857
  }
15790
15858
  function termKey2(t) {
15791
- if (t.op === "lit") return `L:${typeof t.value}:${String(t.value)}`;
15859
+ if (t.op === "lit") return `L:${typeof t.value}:${litKeyString(t.value)}`;
15792
15860
  if (t.op === "var") return `V:${t.id}`;
15793
15861
  return `A:${t.fn}(${t.args.map(termKey2).join(",")})`;
15794
15862
  }
@@ -15904,6 +15972,7 @@ var init_abs_fn = __esm({
15904
15972
  "../core/src/algebra/abs-fn.ts"() {
15905
15973
  "use strict";
15906
15974
  init_abs();
15975
+ init_term();
15907
15976
  implByAbs = /* @__PURE__ */ new WeakMap();
15908
15977
  }
15909
15978
  });
@@ -16258,7 +16327,7 @@ function absShapeKey(a, seen = /* @__PURE__ */ new Set()) {
16258
16327
  try {
16259
16328
  const s = a.shape;
16260
16329
  if (s.k === "prim") {
16261
- if (a.term?.op === "lit") return `prim:${s.type}:${String(a.term.value)}`;
16330
+ if (a.term?.op === "lit") return `prim:${s.type}:${litKeyString(a.term.value)}`;
16262
16331
  const t = a.term ? termToString(a.term) : "";
16263
16332
  const p = a.pred && a.pred.op !== "true" ? predToString(a.pred) : "";
16264
16333
  return `prim:${s.type}:${t}:${p}`;
@@ -16270,7 +16339,7 @@ function absShapeKey(a, seen = /* @__PURE__ */ new Set()) {
16270
16339
  const v2 = a.term.value;
16271
16340
  if (v2 === void 0) return "unknown:undefined";
16272
16341
  if (v2 === null) return "unknown:null";
16273
- return `unknown:lit:${typeof v2}:${String(v2)}`;
16342
+ return `unknown:lit:${typeof v2}:${litKeyString(v2)}`;
16274
16343
  }
16275
16344
  if (a.term) return `unknown:${termToString(a.term)}`;
16276
16345
  return "unknown";
@@ -16952,7 +17021,7 @@ function bindTypeVars(pattern, arg, map, depth = 0) {
16952
17021
  }
16953
17022
  if (ps.k === "obj" && as.k === "obj") {
16954
17023
  for (const [key, slot] of Object.entries(ps.slots)) {
16955
- const aSlot = as.slots[key];
17024
+ const aSlot = getSlot(as.slots, key);
16956
17025
  if (aSlot) bindTypeVars(slot.value, aSlot.value, map, depth + 1);
16957
17026
  }
16958
17027
  return;
@@ -19471,6 +19540,7 @@ function absToJsonNative(a, seen) {
19471
19540
  if (t?.op === "lit") return t.value;
19472
19541
  const s = a.shape;
19473
19542
  if (s.k === "tuple") {
19543
+ if (a.shape.rest) return NOT_LITERAL;
19474
19544
  seen.add(a);
19475
19545
  const holes = a.shape.holes ?? [];
19476
19546
  const out = [];
@@ -21902,51 +21972,15 @@ function toNumericAbs(a) {
21902
21972
  }
21903
21973
  return n;
21904
21974
  }
21905
- function updateAddAbs(a) {
21975
+ function updateAddAbs(a, phi2 = pTrue) {
21906
21976
  const n = toNumericAbs(a);
21907
21977
  const one = n.shape.k === "prim" && n.shape.type === "bigint" ? bigintLit(1n) : numLit(1);
21908
- if (n.shape.k === "prim" && n.shape.type === "bigint" && !n.term) {
21909
- return abs({ k: "prim", type: "bigint" }, void 0, void 0, confJoin(n.conf, "exact"));
21910
- }
21911
- return addNumeric(n, one);
21978
+ return add(n, one, phi2);
21912
21979
  }
21913
- function updateSubAbs(a) {
21980
+ function updateSubAbs(a, phi2 = pTrue) {
21914
21981
  const n = toNumericAbs(a);
21915
21982
  const one = n.shape.k === "prim" && n.shape.type === "bigint" ? bigintLit(1n) : numLit(1);
21916
- if (n.shape.k === "prim" && n.shape.type === "bigint" && !n.term) {
21917
- return abs({ k: "prim", type: "bigint" }, void 0, void 0, confJoin(n.conf, "exact"));
21918
- }
21919
- return subNumeric(n, one);
21920
- }
21921
- function addNumeric(a, b) {
21922
- const ra = litValue(a);
21923
- const rb = litValue(b);
21924
- const va = ra.ok ? ra.value : void 0;
21925
- const vb = rb.ok ? rb.value : void 0;
21926
- if (typeof va === "bigint" && typeof vb === "bigint") return bigintLit(va + vb);
21927
- if (typeof va === "number" && typeof vb === "number") return numLit(va + vb);
21928
- const big = a.shape.k === "prim" && a.shape.type === "bigint" || typeof va === "bigint";
21929
- return abs(
21930
- { k: "prim", type: big ? "bigint" : "number" },
21931
- void 0,
21932
- void 0,
21933
- confJoin(confJoin(a.conf, b.conf), "path")
21934
- );
21935
- }
21936
- function subNumeric(a, b) {
21937
- const ra = litValue(a);
21938
- const rb = litValue(b);
21939
- const va = ra.ok ? ra.value : void 0;
21940
- const vb = rb.ok ? rb.value : void 0;
21941
- if (typeof va === "bigint" && typeof vb === "bigint") return bigintLit(va - vb);
21942
- if (typeof va === "number" && typeof vb === "number") return numLit(va - vb);
21943
- const big = a.shape.k === "prim" && a.shape.type === "bigint" || typeof va === "bigint";
21944
- return abs(
21945
- { k: "prim", type: big ? "bigint" : "number" },
21946
- void 0,
21947
- void 0,
21948
- confJoin(confJoin(a.conf, b.conf), "path")
21949
- );
21983
+ return sub(n, one, phi2);
21950
21984
  }
21951
21985
  function toNumberAbs(a) {
21952
21986
  if (isSymbolAbs(a)) {
@@ -22227,6 +22261,7 @@ var init_surface = __esm({
22227
22261
  init_symbol_id();
22228
22262
  init_term();
22229
22263
  init_pred();
22264
+ init_arithmetic();
22230
22265
  init_nudo_throw();
22231
22266
  init_may_throw();
22232
22267
  }
@@ -23019,24 +23054,40 @@ function leqShape(src, tgt, phi2, env2, depth) {
23019
23054
  return leqWithPred(s.element, t.element, phi2, env2, depth + 1);
23020
23055
  }
23021
23056
  if (s.k === "tuple") {
23022
- return s.elements.every((el) => leqWithPred(el, t.element, phi2, env2, depth + 1).ok) ? ok() : fail("tuple \u22AD arr");
23057
+ const elsOk = s.elements.every((el) => leqWithPred(el, t.element, phi2, env2, depth + 1).ok);
23058
+ const restOk = !s.rest || leqWithPred(s.rest, t.element, phi2, env2, depth + 1).ok;
23059
+ return elsOk && restOk ? ok() : fail("tuple \u22AD arr");
23023
23060
  }
23024
23061
  return fail(`shape ${s.k} \u22AD arr`);
23025
23062
  }
23026
23063
  if (t.k === "tuple") {
23027
23064
  if (s.k !== "tuple") return fail(`shape ${s.k} \u22AD tuple`);
23028
- if (s.elements.length !== t.elements.length) {
23065
+ if (!t.rest && s.elements.length !== t.elements.length) {
23029
23066
  return fail(`tuple arity ${s.elements.length} \u22AD ${t.elements.length}`);
23030
23067
  }
23068
+ if (t.rest && s.elements.length < t.elements.length) {
23069
+ return fail(`tuple arity ${s.elements.length} \u22AD ${t.elements.length} (target requires ${t.elements.length})`);
23070
+ }
23071
+ if (s.rest && !t.rest) {
23072
+ return fail(`tuple rest \u22AD fixed arity ${t.elements.length}`);
23073
+ }
23031
23074
  const sHoles = s.holes ?? [];
23032
23075
  const tHoles = t.holes ?? [];
23033
- if (sHoles.length !== tHoles.length || sHoles.some((h, i) => h !== tHoles[i])) {
23034
- return fail(`tuple holes [${sHoles.join(",")}] \u22AD [${tHoles.join(",")}]`);
23076
+ const overlap = Math.min(s.elements.length, t.elements.length);
23077
+ for (let i = 0; i < overlap; i++) {
23078
+ if (sHoles.includes(i) !== tHoles.includes(i)) {
23079
+ return fail(`tuple holes [${sHoles.join(",")}] \u22AD [${tHoles.join(",")}]`);
23080
+ }
23035
23081
  }
23036
- for (let i = 0; i < t.elements.length; i++) {
23037
- const r = leqWithPred(s.elements[i], t.elements[i], phi2, env2, depth + 1);
23082
+ for (let i = 0; i < s.elements.length; i++) {
23083
+ const tgt2 = i < t.elements.length ? t.elements[i] : t.rest;
23084
+ const r = leqWithPred(s.elements[i], tgt2, phi2, env2, depth + 1);
23038
23085
  if (!r.ok) return fail(`tuple[${i}]: ${r.reason}`);
23039
23086
  }
23087
+ if (s.rest && t.rest) {
23088
+ const r = leqWithPred(s.rest, t.rest, phi2, env2, depth + 1);
23089
+ if (!r.ok) return fail(`tuple rest: ${r.reason}`);
23090
+ }
23040
23091
  return ok();
23041
23092
  }
23042
23093
  if (t.k === "fn") {
@@ -23121,13 +23172,29 @@ function formatAbs(a, opts = {}) {
23121
23172
  return parts.join(" ");
23122
23173
  }
23123
23174
  function formatShapeSlot(a) {
23124
- const base = formatShape(a);
23175
+ return formatShapeSlotB(a, new ProjectionBudget());
23176
+ }
23177
+ function formatShapeSlotB(a, budget) {
23178
+ const base = formatShapeB(a, budget);
23125
23179
  if (!a.term || a.term.op === "lit") return base;
23126
23180
  if (a.shape.k === "fn" || a.shape.k === "arr") return base;
23127
23181
  if (a.shape.k === "any" && a.term.op === "var") return termToString(a.term);
23128
23182
  return `${base} = ${termToString(a.term)}`;
23129
23183
  }
23130
23184
  function formatShape(a) {
23185
+ return formatShapeB(a, new ProjectionBudget());
23186
+ }
23187
+ function formatShapeB(a, budget) {
23188
+ const stop2 = budget.enter(a);
23189
+ if (stop2 === "cycle") return FORMAT_TRUNC.cycle;
23190
+ if (stop2 === "depth") return FORMAT_TRUNC.depth;
23191
+ try {
23192
+ return formatShapeInner(a, budget);
23193
+ } finally {
23194
+ budget.exit();
23195
+ }
23196
+ }
23197
+ function formatShapeInner(a, budget) {
23131
23198
  const s = a.shape;
23132
23199
  switch (s.k) {
23133
23200
  case "never":
@@ -23154,28 +23221,28 @@ function formatShape(a) {
23154
23221
  const entries = Object.entries(s.slots).map(([k, slot]) => {
23155
23222
  const opt = slot.optional ? "?" : "";
23156
23223
  const key = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(k) ? k : JSON.stringify(k);
23157
- return `${key}${opt}: ${formatShape(slot.value)}`;
23224
+ return `${key}${opt}: ${formatShapeB(slot.value, budget)}`;
23158
23225
  });
23159
23226
  return `{ ${entries.join(", ")} }`;
23160
23227
  }
23161
23228
  case "arr": {
23162
23229
  if (s.element.shape.k === "any" && s.element.term?.op === "var") {
23163
- return `arr(${formatShapeSlot(s.element)})`;
23230
+ return `arr(${formatShapeSlotB(s.element, budget)})`;
23164
23231
  }
23165
- return `${formatShape(s.element)}[]`;
23232
+ return `${formatShapeB(s.element, budget)}[]`;
23166
23233
  }
23167
23234
  case "tuple": {
23168
23235
  const holes = s.holes ?? [];
23169
23236
  const parts = s.elements.map(
23170
- (el, i) => holes.includes(i) ? "" : formatShape(el)
23237
+ (el, i) => holes.includes(i) ? "" : formatShapeB(el, budget)
23171
23238
  );
23172
23239
  if (s.rest) {
23173
- parts.push(`...${formatShape(s.rest)}`);
23240
+ parts.push(`...${formatShapeB(s.rest, budget)}`);
23174
23241
  }
23175
23242
  return `[${parts.join(", ")}]`;
23176
23243
  }
23177
23244
  case "fn": {
23178
- const ret = s.returnType !== void 0 ? formatShapeSlot(s.returnType) : "?";
23245
+ const ret = s.returnType !== void 0 ? formatShapeSlotB(s.returnType, budget) : "?";
23179
23246
  const labels = s.params ?? [];
23180
23247
  const paramTypes = s.paramTypes;
23181
23248
  const hasMarkers = labels.some(
@@ -23187,7 +23254,7 @@ function formatShape(a) {
23187
23254
  for (let i = 0; i < n; i++) {
23188
23255
  const label = labels[i];
23189
23256
  const type = paramTypes?.[i];
23190
- const typeText = type !== void 0 ? formatShapeSlot(type) : void 0;
23257
+ const typeText = type !== void 0 ? formatShapeSlotB(type, budget) : void 0;
23191
23258
  if (label?.startsWith("...")) {
23192
23259
  ps.push(typeText ? `...${label.slice(3)}: ${typeText}` : label);
23193
23260
  } else if (label?.endsWith("?")) {
@@ -23204,7 +23271,7 @@ function formatShape(a) {
23204
23271
  return `(${renderLabeled().join(", ")}) => ${ret}`;
23205
23272
  }
23206
23273
  if (paramTypes && paramTypes.length > 0) {
23207
- const ps = paramTypes.map((p) => formatShapeSlot(p));
23274
+ const ps = paramTypes.map((p) => formatShapeSlotB(p, budget));
23208
23275
  return `(${ps.join(", ")}) => ${ret}`;
23209
23276
  }
23210
23277
  return `(${labels.join(", ")}) => ${ret}`;
@@ -23215,7 +23282,7 @@ function formatShape(a) {
23215
23282
  if (slots) {
23216
23283
  const arg = (key) => {
23217
23284
  const sl = slots[key];
23218
- return sl ? formatShapeSlot(sl.value) : void 0;
23285
+ return sl ? formatShapeSlotB(sl.value, budget) : void 0;
23219
23286
  };
23220
23287
  if (s.name === "Map" || s.name === "ReadonlyMap" || s.name === "WeakMap") {
23221
23288
  const k = arg("__key");
@@ -23230,12 +23297,12 @@ function formatShape(a) {
23230
23297
  return `${s.name}`;
23231
23298
  }
23232
23299
  case "eff":
23233
- return `${s.eff}<${formatShape(s.inner)}>`;
23300
+ return `${s.eff}<${formatShapeB(s.inner, budget)}>`;
23234
23301
  case "sum": {
23235
23302
  const seen = /* @__PURE__ */ new Set();
23236
23303
  const parts = [];
23237
23304
  for (const m of s.members) {
23238
- const t = formatShape(m);
23305
+ const t = formatShapeB(m, budget);
23239
23306
  if (seen.has(t)) continue;
23240
23307
  seen.add(t);
23241
23308
  parts.push(t);
@@ -23259,12 +23326,15 @@ function formatAbsMultiline(a, label) {
23259
23326
  lines.push(` conf: ${a.conf}`);
23260
23327
  return lines.join("\n");
23261
23328
  }
23329
+ var FORMAT_TRUNC;
23262
23330
  var init_format = __esm({
23263
23331
  "../core/src/algebra/format.ts"() {
23264
23332
  "use strict";
23265
23333
  init_abs();
23266
23334
  init_term();
23267
23335
  init_pred();
23336
+ init_projection_budget();
23337
+ FORMAT_TRUNC = { cycle: "\u2026cycle", depth: "\u2026" };
23268
23338
  }
23269
23339
  });
23270
23340
 
@@ -23938,12 +24008,19 @@ function parseNudoImportPayload(payload) {
23938
24008
  if (s === "") return void 0;
23939
24009
  const named = s.match(/^\{([^}]+)\}\s*from\s*["']([^"']+)["']$/);
23940
24010
  if (named) {
23941
- const names2 = named[1].split("\n").map((line) => line.replace(/^\s*\/\/\/?\s?/, "").trim()).join("\n").split(",").map((x) => x.trim()).filter(Boolean).map((x) => x.split(/\s+as\s+/).pop().trim());
23942
- return { names: names2, spec: named[2] };
24011
+ const names2 = [];
24012
+ const importedOfLocal = {};
24013
+ for (const x of named[1].split("\n").map((line) => line.replace(/^\s*\/\/\/?\s?/, "").trim()).join("\n").split(",").map((x2) => x2.trim()).filter(Boolean)) {
24014
+ const seg = x.split(/\s+as\s+/);
24015
+ const local = seg[seg.length - 1].trim();
24016
+ names2.push(local);
24017
+ if (seg.length > 1) importedOfLocal[local] = seg[0].trim();
24018
+ }
24019
+ return { names: names2, importedOfLocal, spec: named[2] };
23943
24020
  }
23944
24021
  const ns = s.match(/^\*\s+as\s+(\w+)\s+from\s*["']([^"']+)["']$/);
23945
24022
  if (ns) {
23946
- return { names: [`*${ns[1]}`], spec: ns[2] };
24023
+ return { names: [`*${ns[1]}`], importedOfLocal: {}, spec: ns[2] };
23947
24024
  }
23948
24025
  if (/^[\w$]+\s+from\b/.test(s) || /^[\w$]+\s*,/.test(s)) return "malformed-default";
23949
24026
  return "malformed";
@@ -23953,7 +24030,7 @@ function fileDirectiveVisibleText(source) {
23953
24030
  }
23954
24031
  function extractFileEnvNames(source) {
23955
24032
  const text = fileDirectiveVisibleText(source);
23956
- const re = new RegExp(`${LINE_COMMENT_PREFIX}\\s*@nudo:env\\s+([^\\n*]+)`, "g");
24033
+ const re = new RegExp(`${LINE_COMMENT_PREFIX}[ \\t]*@nudo:env[ \\t]+([^\\n*]+)`, "g");
23957
24034
  const out = [];
23958
24035
  let m;
23959
24036
  while (m = re.exec(text)) {
@@ -23964,7 +24041,7 @@ function extractFileEnvNames(source) {
23964
24041
  function extractMockModuleRecords(source) {
23965
24042
  const text = fileDirectiveVisibleText(source);
23966
24043
  const re = new RegExp(
23967
- `${LINE_COMMENT_PREFIX}\\s*@nudo:mock-module\\s+([^\\n]+)`,
24044
+ `${LINE_COMMENT_PREFIX}[ \\t]*@nudo:mock-module[ \\t]+([^\\n]+)`,
23968
24045
  "g"
23969
24046
  );
23970
24047
  const out = [];
@@ -24165,7 +24242,7 @@ var init_directive_scan = __esm({
24165
24242
  init_parse_source();
24166
24243
  isPathLikeEnv = (s) => /^[^\s]+$/.test(s) && (s.startsWith("./") || s.startsWith("../") || s.startsWith("/") || /\.(ts|js|mjs|cjs|tsx|jsx)$/.test(s));
24167
24244
  LINE_COMMENT_PREFIX = "(?:^|[^/:])\\/\\/\\/?";
24168
- CASE_NAME_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*|\/\/\/?\s*)?@nudo:case\s+"([^"]+)"\s*\(/g;
24245
+ CASE_NAME_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*|\/[ \t]*)?@nudo:case[ \t]+"([^"\n]+)"[ \t]*\(/g;
24169
24246
  }
24170
24247
  });
24171
24248
 
@@ -24782,11 +24859,7 @@ var init_constraint = __esm({
24782
24859
  });
24783
24860
 
24784
24861
  // ../core/src/algebra/refine.ts
24785
- function extractNudoImports(source) {
24786
- const out = [];
24787
- for (const rec of extractNudoImportRecords(source)) {
24788
- out.push({ names: rec.names, spec: rec.spec });
24789
- }
24862
+ function diagnoseMalformedNudoImports(source) {
24790
24863
  for (const bad of scanMalformedNudoImports(source)) {
24791
24864
  if (bad === "malformed-default") {
24792
24865
  collectDiag({
@@ -24800,7 +24873,13 @@ function extractNudoImports(source) {
24800
24873
  });
24801
24874
  }
24802
24875
  }
24803
- return out;
24876
+ }
24877
+ function extractNudoImports(source) {
24878
+ diagnoseMalformedNudoImports(source);
24879
+ return extractNudoImportRecords(source).map((rec) => ({
24880
+ names: rec.names,
24881
+ spec: rec.spec
24882
+ }));
24804
24883
  }
24805
24884
  function setRefineDiagCollector(fn2) {
24806
24885
  refineDiagCollector = fn2;
@@ -24897,6 +24976,21 @@ function rewriteSidecarSource(src, fromFile) {
24897
24976
  cuts.push({ start: stmt.start, end: decl.start, text: "" });
24898
24977
  continue;
24899
24978
  }
24979
+ if (!stmt.source) {
24980
+ let aliasPairs = false;
24981
+ for (const spec of stmt.specifiers) {
24982
+ if (spec.type !== "ExportSpecifier") continue;
24983
+ if (spec.local.type !== "Identifier") continue;
24984
+ const key = spec.exported.type === "Identifier" ? spec.exported.name : spec.exported.value;
24985
+ if (key === void 0) continue;
24986
+ exportPairs.push({ key, expr: spec.local.name });
24987
+ aliasPairs = true;
24988
+ }
24989
+ if (aliasPairs) {
24990
+ cuts.push({ start: stmt.start, end: stmt.end, text: "" });
24991
+ continue;
24992
+ }
24993
+ }
24900
24994
  badExport("'export { \u2026 }' (list / re-export)");
24901
24995
  if (stmt.start != null && stmt.end != null) {
24902
24996
  cuts.push({ start: stmt.start, end: stmt.end, text: "" });
@@ -25053,14 +25147,15 @@ function execSidecar(src, opts, chain, preloaded) {
25053
25147
  });
25054
25148
  prologue.push(`var ${n.local} = __nudoInjects;`);
25055
25149
  } else if (n.imported !== void 0) {
25056
- if (!(n.imported in sidecarInjects)) {
25150
+ const hasOwnInject = Object.hasOwn(sidecarInjects, n.imported);
25151
+ if (!hasOwnInject) {
25057
25152
  collectDiag({
25058
25153
  code: "nudo:interface-load",
25059
25154
  message: `sidecar import '{ ${n.imported} }' from '${imp.spec}' is not an injected builder`,
25060
25155
  file: fromFile
25061
25156
  });
25062
- }
25063
- if (n.local !== n.imported || !(n.imported in sidecarInjects)) {
25157
+ prologue.push(`var ${n.local} = undefined;`);
25158
+ } else if (n.local !== n.imported) {
25064
25159
  prologue.push(`var ${n.local} = __nudoInjects[${JSON.stringify(n.imported)}];`);
25065
25160
  }
25066
25161
  } else {
@@ -25129,21 +25224,23 @@ function execSidecar(src, opts, chain, preloaded) {
25129
25224
  if (n.ns) {
25130
25225
  deps[n.local] = depExports;
25131
25226
  } else if (n.imported !== void 0) {
25132
- if (!(n.imported in depExports)) {
25227
+ if (Object.hasOwn(depExports, n.imported)) {
25228
+ deps[n.local] = depExports[n.imported];
25229
+ } else {
25133
25230
  collectDiag({
25134
25231
  code: "nudo:interface-load",
25135
25232
  message: `sidecar '${imp.spec}' has no export '${n.imported}'`,
25136
25233
  file: fromFile
25137
25234
  });
25235
+ deps[n.local] = void 0;
25138
25236
  }
25139
- deps[n.local] = depExports[n.imported];
25140
25237
  } else {
25141
25238
  collectDiag({
25142
25239
  code: "nudo:interface-load",
25143
25240
  message: `sidecar default import '${n.local}' from '${imp.spec}' has no default export (sidecars export named constraints only)`,
25144
25241
  file: fromFile
25145
25242
  });
25146
- deps[n.local] = depExports.default;
25243
+ deps[n.local] = Object.hasOwn(depExports, "default") ? depExports.default : void 0;
25147
25244
  }
25148
25245
  }
25149
25246
  }
@@ -25247,7 +25344,8 @@ function resetNudoModuleExecCache() {
25247
25344
  }
25248
25345
  function collectConstraints(source, opts) {
25249
25346
  const map = /* @__PURE__ */ new Map();
25250
- const imports = extractNudoImports(source);
25347
+ diagnoseMalformedNudoImports(source);
25348
+ const imports = extractNudoImportRecords(source);
25251
25349
  for (const imp of imports) {
25252
25350
  if (!opts.loadModule || !opts.fromFile) {
25253
25351
  collectDiag({
@@ -25302,19 +25400,20 @@ function collectConstraints(source, opts) {
25302
25400
  }
25303
25401
  continue;
25304
25402
  }
25305
- const v2 = exports[name];
25403
+ const imported = imp.importedOfLocal[name] ?? name;
25404
+ const v2 = Object.hasOwn(exports, imported) ? exports[imported] : void 0;
25306
25405
  if (isNudoConstraint(v2)) {
25307
25406
  map.set(name, v2);
25308
25407
  } else if (v2 === void 0) {
25309
25408
  collectDiag({
25310
25409
  code: "nudo:interface-load",
25311
- message: `sidecar '${imp.spec}' has no export '${name}'`,
25410
+ message: `sidecar '${imp.spec}' has no export '${imported}'`,
25312
25411
  file: opts.fromFile
25313
25412
  });
25314
25413
  } else {
25315
25414
  collectDiag({
25316
25415
  code: "nudo:interface-load",
25317
- message: `sidecar '${imp.spec}' export '${name}' is not a Nudo constraint`,
25416
+ message: `sidecar '${imp.spec}' export '${imported}' is not a Nudo constraint`,
25318
25417
  file: opts.fromFile
25319
25418
  });
25320
25419
  }
@@ -25345,7 +25444,13 @@ function extractRefinesFromSource(source, fnName, opts = {}) {
25345
25444
  const [, param, cName] = m;
25346
25445
  if (param === "return") continue;
25347
25446
  const c = constraints.get(cName);
25348
- if (!c) continue;
25447
+ if (!c) {
25448
+ collectDiag({
25449
+ code: "nudo:contract-syntax",
25450
+ message: `Unknown constraint name '${cName}' in @nudo:contract (expected a name bound by @nudo:import)`
25451
+ });
25452
+ continue;
25453
+ }
25349
25454
  out.push({
25350
25455
  param,
25351
25456
  pred: instantiateConstraint(c, param),
@@ -25382,7 +25487,13 @@ function extractRefineReturnFromSource(source, fnName, opts = {}) {
25382
25487
  }
25383
25488
  const cName = m[1];
25384
25489
  const c = constraints.get(cName);
25385
- if (!c) continue;
25490
+ if (!c) {
25491
+ collectDiag({
25492
+ code: "nudo:contract-syntax",
25493
+ message: `Unknown constraint name '${cName}' in @nudo:contract return (expected a name bound by @nudo:import)`
25494
+ });
25495
+ continue;
25496
+ }
25386
25497
  return { name: cName, constraint: c };
25387
25498
  }
25388
25499
  }
@@ -25451,8 +25562,10 @@ var init_unsupported = __esm({
25451
25562
  NudoUnsupportedError = class extends Error {
25452
25563
  reason;
25453
25564
  loc;
25454
- constructor(reason, loc) {
25455
- super(`nudo:unsupported ${reason}`);
25565
+ constructor(reason, loc, detail) {
25566
+ super(
25567
+ detail ? `nudo:unsupported ${reason} (${detail})` : `nudo:unsupported ${reason}`
25568
+ );
25456
25569
  this.name = "NudoUnsupportedError";
25457
25570
  this.reason = reason;
25458
25571
  this.loc = loc;
@@ -26911,15 +27024,15 @@ function transpileStatement(stmt, depth, opts) {
26911
27024
  if (nsSpecs.length > 0) {
26912
27025
  if (!stmt.source) return `${pad}/* export * as without source skipped */`;
26913
27026
  return nsSpecs.map((s) => {
26914
- const exported = s.exported.type === "Identifier" ? s.exported.name : s.exported.value;
27027
+ const exported = s.exported.type === "Identifier" ? s.exported.name : JSON.stringify(s.exported.value);
26915
27028
  return `${pad}export * as ${exported} from ${JSON.stringify(stmt.source.value)};`;
26916
27029
  }).join("\n");
26917
27030
  }
26918
27031
  const specs = stmt.specifiers.filter(
26919
27032
  (s) => "local" in s
26920
27033
  ).map((s) => {
26921
- const local = s.local.type === "Identifier" ? s.local.name : s.local.value;
26922
- const exported = s.exported.type === "Identifier" ? s.exported.name : s.exported.value;
27034
+ const local = s.local.type === "Identifier" ? s.local.name : JSON.stringify(s.local.value);
27035
+ const exported = s.exported.type === "Identifier" ? s.exported.name : JSON.stringify(s.exported.value);
26923
27036
  return local === exported ? local : `${local} as ${exported}`;
26924
27037
  }).join(", ");
26925
27038
  if (stmt.source) {
@@ -28886,10 +28999,10 @@ function $toNumeric(a) {
28886
28999
  return toNumericAbs(a);
28887
29000
  }
28888
29001
  function $updateAdd(a) {
28889
- return updateAddAbs(a);
29002
+ return updateAddAbs(a, currentExecPhi());
28890
29003
  }
28891
29004
  function $updateSub(a) {
28892
- return updateSubAbs(a);
29005
+ return updateSubAbs(a, currentExecPhi());
28893
29006
  }
28894
29007
  function $neg(a) {
28895
29008
  return negAbs(a);
@@ -29153,21 +29266,51 @@ var init_member_diag = __esm({
29153
29266
  });
29154
29267
 
29155
29268
  // ../core/src/algebra/exec/class-registry.ts
29269
+ function beginClassEpoch() {
29270
+ classEpoch++;
29271
+ }
29272
+ function drainClassCollisions() {
29273
+ return classCollisions.splice(0, classCollisions.length);
29274
+ }
29275
+ function specShape(spec) {
29276
+ const ctorName = spec.ctor?.name ?? "anon";
29277
+ const methods2 = Object.keys(spec.methods ?? {}).sort().join(",");
29278
+ const statics = Object.keys(spec.staticMethods ?? {}).sort().join(",");
29279
+ const accessors = Object.keys(spec.accessors ?? {}).sort().join(",");
29280
+ return `${ctorName}[${methods2}](${statics}){${accessors}}`;
29281
+ }
29156
29282
  function registerEvalClass(spec) {
29283
+ const prev = classRegistry.get(spec.name);
29284
+ const prevEpoch = classEpochs.get(spec.name);
29285
+ if (prev && prev !== spec && // 同 epoch(同一次 run 内)注册两次才可能是真碰撞:
29286
+ // 块级作用域同名类。跨 epoch = 顺序重注册(常态)。
29287
+ prevEpoch === classEpoch && specShape(prev) !== specShape(spec)) {
29288
+ classCollisions.push({
29289
+ name: spec.name,
29290
+ previous: specShape(prev),
29291
+ next: specShape(spec)
29292
+ });
29293
+ }
29157
29294
  classRegistry.set(spec.name, spec);
29295
+ classEpochs.set(spec.name, classEpoch);
29158
29296
  }
29159
29297
  function getEvalClass(name) {
29160
29298
  return classRegistry.get(name);
29161
29299
  }
29162
29300
  function clearBClasses() {
29163
29301
  classRegistry.clear();
29302
+ classEpochs.clear();
29303
+ classCollisions.length = 0;
29164
29304
  }
29165
- var classRegistry;
29305
+ var classRegistry, classEpoch, classEpochs, classCollisions;
29166
29306
  var init_class_registry = __esm({
29167
29307
  "../core/src/algebra/exec/class-registry.ts"() {
29168
29308
  "use strict";
29169
29309
  init_class_mark();
29170
29310
  classRegistry = /* @__PURE__ */ new Map();
29311
+ classEpoch = 0;
29312
+ classEpochs = /* @__PURE__ */ new Map();
29313
+ classCollisions = [];
29171
29314
  }
29172
29315
  });
29173
29316
 
@@ -29691,6 +29834,8 @@ function $copy(a) {
29691
29834
  {
29692
29835
  k: "tuple",
29693
29836
  elements: s.elements.map($copy),
29837
+ // rest 槽必须随快照迁移(与 hof snapshotAbs 同口径),否则副本丢尾段
29838
+ ...s.rest ? { rest: $copy(s.rest) } : {},
29694
29839
  holes: s.holes ? [...s.holes] : void 0
29695
29840
  },
29696
29841
  a.term,
@@ -29881,7 +30026,7 @@ function $idx(a, i) {
29881
30026
  if (slots.length === 0) return objShape.open ? unknown : undef();
29882
30027
  const joinSlotsWithUndef = () => joinAbs(slots.reduce((x, y) => joinAbs(x, y)), undef());
29883
30028
  if (typeof iv === "string" || typeof iv === "number" || typeof iv === "boolean") {
29884
- const slot = objShape.slots[String(iv)];
30029
+ const slot = getSlot(objShape.slots, String(iv));
29885
30030
  if (slot) return slot.value;
29886
30031
  if (objShape.open) return unknown;
29887
30032
  return undef();
@@ -30098,6 +30243,20 @@ function $arrRest(a, start) {
30098
30243
  return parts.length ? parts.reduce((x, y) => joinAbs(x, y)) : unknown;
30099
30244
  }
30100
30245
  if (a.shape.k === "tuple") {
30246
+ if (a.shape.rest) {
30247
+ const sliced = a.shape.elements.slice(start);
30248
+ if (start < a.shape.elements.length && !shouldWidenArrayLiteral(sliced.length + 1)) {
30249
+ return abs(
30250
+ { k: "tuple", elements: sliced, rest: a.shape.rest },
30251
+ void 0,
30252
+ void 0,
30253
+ a.conf
30254
+ );
30255
+ }
30256
+ const restEl = a.shape.rest.shape.k === "arr" ? a.shape.rest.shape.element : a.shape.rest;
30257
+ const el = sliced.reduce((x, y) => joinAbs(x, y), restEl);
30258
+ return abs({ k: "arr", element: el }, void 0, void 0, a.conf);
30259
+ }
30101
30260
  return tupleOrWiden(a.shape.elements.slice(start), a.conf);
30102
30261
  }
30103
30262
  if (a.shape.k === "arr") return a;
@@ -32610,7 +32769,7 @@ function namespaceAbsOf(mod2) {
32610
32769
  return abs({ k: "obj", slots, open: true }, void 0, void 0, "path");
32611
32770
  }
32612
32771
  function bindImports(node, env2, modules) {
32613
- const mod2 = modules[node.source.value];
32772
+ const mod2 = Object.hasOwn(modules, node.source.value) ? modules[node.source.value] : void 0;
32614
32773
  if (!mod2) {
32615
32774
  for (const s of node.specifiers) {
32616
32775
  env2.vars.set(s.local.name, unknown);
@@ -32622,7 +32781,8 @@ function bindImports(node, env2, modules) {
32622
32781
  env2.vars.set(s.local.name, mod2.default ?? unknown);
32623
32782
  } else if (s.type === "ImportSpecifier") {
32624
32783
  const imported = s.imported.type === "Identifier" ? s.imported.name : String(s.imported);
32625
- env2.vars.set(s.local.name, mod2.named[imported] ?? unknown);
32784
+ const named = Object.hasOwn(mod2.named, imported) ? mod2.named[imported] : void 0;
32785
+ env2.vars.set(s.local.name, named ?? unknown);
32626
32786
  } else if (s.type === "ImportNamespaceSpecifier") {
32627
32787
  env2.vars.set(s.local.name, namespaceAbsOf(mod2));
32628
32788
  }
@@ -32648,7 +32808,7 @@ function collectAbsExports(file, env2, modules) {
32648
32808
  for (const stmt of file.program.body) {
32649
32809
  if (stmt.type === "ExportNamedDeclaration") {
32650
32810
  if (stmt.source && modules) {
32651
- const mod2 = modules[stmt.source.value];
32811
+ const mod2 = Object.hasOwn(modules, stmt.source.value) ? modules[stmt.source.value] : void 0;
32652
32812
  if (mod2) {
32653
32813
  for (const spec of stmt.specifiers) {
32654
32814
  if (spec.type === "ExportNamespaceSpecifier") {
@@ -32660,7 +32820,7 @@ function collectAbsExports(file, env2, modules) {
32660
32820
  if (spec.type !== "ExportSpecifier") continue;
32661
32821
  const local = spec.local.type === "Identifier" ? spec.local.name : spec.local.value;
32662
32822
  const exported = spec.exported.type === "Identifier" ? spec.exported.name : spec.exported.value;
32663
- const v2 = local === "default" ? mod2.default : mod2.named[local];
32823
+ const v2 = local === "default" ? mod2.default : Object.hasOwn(mod2.named, local) ? mod2.named[local] : void 0;
32664
32824
  const slot = v2 ?? unknown;
32665
32825
  if (exported === "default") defaultExport = slot;
32666
32826
  else {
@@ -32706,7 +32866,7 @@ function collectAbsExports(file, env2, modules) {
32706
32866
  }
32707
32867
  }
32708
32868
  } else if (stmt.type === "ExportAllDeclaration" && stmt.source && modules) {
32709
- const mod2 = modules[stmt.source.value];
32869
+ const mod2 = Object.hasOwn(modules, stmt.source.value) ? modules[stmt.source.value] : void 0;
32710
32870
  if (mod2?.named) {
32711
32871
  for (const [k, v2] of Object.entries(mod2.named)) {
32712
32872
  if (!explicitNames.has(k)) named[k] = v2;
@@ -33061,6 +33221,7 @@ ${js}`;
33061
33221
  const fn2 = new Function(...argNames, `${js}
33062
33222
  ${ret}`);
33063
33223
  setEvalBindingSink(bindings);
33224
+ beginClassEpoch();
33064
33225
  try {
33065
33226
  const result = fn2(...args);
33066
33227
  runBindings.set(result, bindings);
@@ -33068,6 +33229,18 @@ ${ret}`);
33068
33229
  } finally {
33069
33230
  setEvalBindingSink(null);
33070
33231
  drainPromiseMicros();
33232
+ noteDrainedClassCollisions();
33233
+ }
33234
+ }
33235
+ function noteDrainedClassCollisions() {
33236
+ for (const c of drainClassCollisions()) {
33237
+ noteEvalFallback(
33238
+ new NudoUnsupportedError(
33239
+ "class-collision",
33240
+ void 0,
33241
+ `class '${c.name}' registered again with a different shape (${c.previous} -> ${c.next}); name-keyed lookup may resolve to either definition`
33242
+ )
33243
+ );
33071
33244
  }
33072
33245
  }
33073
33246
  function setEvalFallbackCollector(collector2) {
@@ -33125,14 +33298,16 @@ function isAbsVal(v2) {
33125
33298
  }
33126
33299
  function callTranspiledExportFull(exports, name, args, opts) {
33127
33300
  enterEvalCallBudgetSession();
33301
+ beginClassEpoch();
33128
33302
  try {
33129
33303
  return callTranspiledExportFullInner(exports, name, args, opts);
33130
33304
  } finally {
33131
33305
  exitEvalCallBudgetSession();
33306
+ noteDrainedClassCollisions();
33132
33307
  }
33133
33308
  }
33134
33309
  function callTranspiledExportFullInner(exports, name, args, opts) {
33135
- const fn2 = exports[name];
33310
+ const fn2 = Object.hasOwn(exports, name) ? exports[name] : void 0;
33136
33311
  if (typeof fn2 === "function") {
33137
33312
  const callArgs = args.map(
33138
33313
  (a) => a && typeof a === "object" && "shape" in a ? $copy(a) : a
@@ -33236,6 +33411,7 @@ var init_run = __esm({
33236
33411
  init_transpile();
33237
33412
  init_intrinsics();
33238
33413
  init_unsupported();
33414
+ init_class_registry();
33239
33415
  init_export_names();
33240
33416
  init_may_throw();
33241
33417
  init_builtins();
@@ -33949,7 +34125,9 @@ function localNamedExports(source) {
33949
34125
  const identName = (n) => {
33950
34126
  if (!n) return void 0;
33951
34127
  if (n.type === "Identifier" && typeof n.name === "string") return n.name;
33952
- if (n.type === "Literal" && typeof n.value === "string") return n.value;
34128
+ if ((n.type === "StringLiteral" || n.type === "Literal") && typeof n.value === "string") {
34129
+ return n.value;
34130
+ }
33953
34131
  return void 0;
33954
34132
  };
33955
34133
  const localClasses = /* @__PURE__ */ new Map();
@@ -34097,6 +34275,25 @@ function localNamedExports(source) {
34097
34275
  }
34098
34276
  return out;
34099
34277
  }
34278
+ function exportedNameOfLocal(source, localName) {
34279
+ let ast;
34280
+ try {
34281
+ ast = parseSource(source, { errorRecovery: true });
34282
+ } catch {
34283
+ return void 0;
34284
+ }
34285
+ for (const stmt of ast.program.body) {
34286
+ if (stmt.type !== "ExportNamedDeclaration" || stmt.source) continue;
34287
+ for (const spec of stmt.specifiers) {
34288
+ if (spec.type !== "ExportSpecifier") continue;
34289
+ if (spec.local.type !== "Identifier" || spec.local.name !== localName) continue;
34290
+ const name = spec.exported.type === "Identifier" ? spec.exported.name : spec.exported.value;
34291
+ if (name === void 0 || name === localName || name === "default") continue;
34292
+ return name;
34293
+ }
34294
+ }
34295
+ return void 0;
34296
+ }
34100
34297
  function generatedExportNames(sidecarSrc) {
34101
34298
  const out = /* @__PURE__ */ new Set();
34102
34299
  let ast;
@@ -34110,8 +34307,18 @@ function generatedExportNames(sidecarSrc) {
34110
34307
  const stmt = stmts[i];
34111
34308
  if (stmt.type !== "ExportNamedDeclaration") continue;
34112
34309
  if (stmt.source) continue;
34310
+ if (stmt.start == null) continue;
34113
34311
  const d = stmt.declaration;
34114
- if (!d || stmt.start == null) continue;
34312
+ if (!d) {
34313
+ if (regionHasGeneratedMarker(stmts, i, sidecarSrc)) {
34314
+ for (const spec of stmt.specifiers) {
34315
+ if (spec.type !== "ExportSpecifier") continue;
34316
+ const name = spec.exported.type === "Identifier" ? spec.exported.name : spec.exported.value;
34317
+ if (name !== void 0) out.add(name);
34318
+ }
34319
+ }
34320
+ continue;
34321
+ }
34115
34322
  const names2 = [];
34116
34323
  if (d.type === "VariableDeclaration") {
34117
34324
  for (const decl of d.declarations) {
@@ -34182,6 +34389,8 @@ function sidecarMayBind(sidecarNames, fnName, source) {
34182
34389
  if (!sidecarNames) return true;
34183
34390
  if (sidecarNames.size === 0) return true;
34184
34391
  if (sidecarNames.has(fnName)) return true;
34392
+ const aliasExport = exportedNameOfLocal(source, fnName);
34393
+ if (aliasExport !== void 0 && sidecarNames.has(aliasExport)) return true;
34185
34394
  if (sidecarNames.has("default") && isDefaultExportLocal(source, fnName)) return true;
34186
34395
  if (fnName.includes(".")) {
34187
34396
  const [cls, method] = fnName.split(".", 2);
@@ -34251,20 +34460,26 @@ function loadSidecarBinding(source, fnName, opts) {
34251
34460
  }
34252
34461
  return { ok: false };
34253
34462
  }
34254
- let binding = exports[fnName];
34463
+ let binding = Object.hasOwn(exports, fnName) ? exports[fnName] : void 0;
34464
+ if (binding === void 0) {
34465
+ const aliasExport2 = exportedNameOfLocal(source, fnName);
34466
+ if (aliasExport2 !== void 0 && Object.hasOwn(exports, aliasExport2)) {
34467
+ binding = exports[aliasExport2];
34468
+ }
34469
+ }
34255
34470
  if (binding === void 0 && exports.default !== void 0 && isDefaultExportLocal(source, fnName)) {
34256
34471
  binding = exports.default;
34257
34472
  }
34258
34473
  if (binding === void 0 && fnName.includes(".")) {
34259
34474
  const [cls, method] = fnName.split(".", 2);
34260
- binding = exports[`${cls}_${method}`];
34475
+ binding = Object.hasOwn(exports, `${cls}_${method}`) ? exports[`${cls}_${method}`] : void 0;
34261
34476
  if (binding === void 0) {
34262
- const bag = exports[cls];
34477
+ const bag = Object.hasOwn(exports, cls) ? exports[cls] : void 0;
34263
34478
  if (bag && typeof bag === "object" && !isNudoConstraint(bag)) {
34264
- binding = bag[method];
34479
+ binding = Object.hasOwn(bag, method) ? bag[method] : void 0;
34265
34480
  }
34266
34481
  }
34267
- if (binding === void 0 && method !== void 0 && exports[method] !== void 0) {
34482
+ if (binding === void 0 && method !== void 0 && Object.hasOwn(exports, method)) {
34268
34483
  collectDiag2({
34269
34484
  code: "nudo:interface-load",
34270
34485
  message: `sidecar key '${method}' does not bind '${fnName}' \u2014 use '${cls}.${method}', '${cls}_${method}', or nested { ${cls}: { ${method}: \u2026 } }`,
@@ -34290,7 +34505,8 @@ function loadSidecarBinding(source, fnName, opts) {
34290
34505
  return { ok: false };
34291
34506
  }
34292
34507
  const generatedNames = generatedExportNames(sidecarSrc);
34293
- const isGenerated2 = generatedNames.has(fnName) || (fnName.includes(".") ? (() => {
34508
+ const aliasExport = exportedNameOfLocal(source, fnName);
34509
+ const isGenerated2 = generatedNames.has(fnName) || (aliasExport !== void 0 ? generatedNames.has(aliasExport) : false) || (fnName.includes(".") ? (() => {
34294
34510
  const [cls, method] = fnName.split(".", 2);
34295
34511
  return generatedNames.has(`${cls}_${method}`) || generatedNames.has(cls);
34296
34512
  })() : false);
@@ -35216,7 +35432,7 @@ var init_promote_scan = __esm({
35216
35432
  function termKey5(t, rename) {
35217
35433
  switch (t.op) {
35218
35434
  case "lit":
35219
- return `L:${typeof t.value}:${String(t.value)}`;
35435
+ return `L:${typeof t.value}:${litKeyString(t.value)}`;
35220
35436
  case "var":
35221
35437
  return `V:${rename?.get(t.id) ?? t.id}`;
35222
35438
  case "app":
@@ -35323,6 +35539,9 @@ function collectPredVars(p, acc) {
35323
35539
  case "typeof":
35324
35540
  collectTermVars(p.t, acc);
35325
35541
  return;
35542
+ case "assumeFinite":
35543
+ collectTermVars(p.t, acc);
35544
+ return;
35326
35545
  }
35327
35546
  }
35328
35547
  function collectAbsVars(a, acc, seen) {
@@ -37705,7 +37924,7 @@ function scanLiteralCalls(source, knownFns, phi2, opts) {
37705
37924
  if (absArg.shape.k === "unknown" && !absArg.term) return;
37706
37925
  const slots = absArg.shape.k === "obj" ? absArg.shape.slots : void 0;
37707
37926
  if (!slots) {
37708
- if (absArg.shape.k !== "unknown" && absArg.shape.k !== "never") {
37927
+ if (absArg.shape.k !== "unknown" && absArg.shape.k !== "never" && absArg.shape.k !== "any") {
37709
37928
  out.push({
37710
37929
  severity: "error",
37711
37930
  code: "nudo:constraint-violated",
@@ -38282,8 +38501,17 @@ var init_scan = __esm({
38282
38501
  });
38283
38502
 
38284
38503
  // ../core/src/algebra/projection.ts
38285
- function absToConstraint(a) {
38504
+ function absToConstraint(a, budget = new ProjectionBudget()) {
38286
38505
  if (!PROJECTABLE_CONF.has(a.conf)) return void 0;
38506
+ const stop2 = budget.enter(a);
38507
+ if (stop2) return void 0;
38508
+ try {
38509
+ return absToConstraintInner(a, budget);
38510
+ } finally {
38511
+ budget.exit();
38512
+ }
38513
+ }
38514
+ function absToConstraintInner(a, budget) {
38287
38515
  const s = a.shape;
38288
38516
  switch (s.k) {
38289
38517
  case "prim":
@@ -38301,9 +38529,9 @@ function absToConstraint(a) {
38301
38529
  case "any":
38302
38530
  return a.pred?.op === "or" ? projectOrLiterals(a) : void 0;
38303
38531
  case "obj":
38304
- return projectObj(s.slots, s.index, s.open);
38532
+ return projectObj(s.slots, s.index, s.open, budget);
38305
38533
  case "arr":
38306
- return projectArr(s.element);
38534
+ return projectArr(s.element, budget);
38307
38535
  case "sum":
38308
38536
  return projectSum(s.members);
38309
38537
  default:
@@ -38486,18 +38714,18 @@ function projectOrLiterals(a) {
38486
38714
  }
38487
38715
  return lits.length > 0 ? union(...lits) : void 0;
38488
38716
  }
38489
- function projectObj(slots, index2, open) {
38717
+ function projectObj(slots, index2, open, budget) {
38490
38718
  if (index2 || open) return void 0;
38491
38719
  const fields = {};
38492
38720
  for (const [k, slot] of Object.entries(slots)) {
38493
- const fc = absToConstraint(slot.value);
38721
+ const fc = absToConstraint(slot.value, budget);
38494
38722
  if (!fc) return void 0;
38495
38723
  fields[k] = { constraint: fc, ...slot.optional ? { optional: true } : {} };
38496
38724
  }
38497
38725
  return { __nudoConstraint: true, preds: [], fields };
38498
38726
  }
38499
- function projectArr(element) {
38500
- const ec = absToConstraint(element);
38727
+ function projectArr(element, budget) {
38728
+ const ec = absToConstraint(element, budget);
38501
38729
  if (ec) return array(ec);
38502
38730
  if (element.shape.k === "prim" && isBuilderPrim(element.shape.type) && (!element.pred || element.pred.op === "true") && ARR_FALLBACK_CONF.has(element.conf)) {
38503
38731
  return array(primConstraint(element.shape.type));
@@ -38607,6 +38835,7 @@ var init_projection = __esm({
38607
38835
  init_objects();
38608
38836
  init_term();
38609
38837
  init_term();
38838
+ init_projection_budget();
38610
38839
  PROJECTABLE_CONF = /* @__PURE__ */ new Set(["exact", "path"]);
38611
38840
  ARR_FALLBACK_CONF = /* @__PURE__ */ new Set([
38612
38841
  "exact",
@@ -39045,7 +39274,7 @@ function evalThrowsOf(source, fnName, args, opts = {}, phi2 = pTrue) {
39045
39274
  }
39046
39275
  };
39047
39276
  try {
39048
- if (fnName in exports) return call(fnName, args);
39277
+ if (Object.hasOwn(exports, fnName)) return call(fnName, args);
39049
39278
  if (fnName.includes(".")) {
39050
39279
  const [clsName, methodName] = fnName.split(".", 2);
39051
39280
  const clsAbs = exports[clsName ?? ""];
@@ -39091,12 +39320,9 @@ function widenForAssign(a) {
39091
39320
  if (s.k === "tuple") {
39092
39321
  const holes = new Set(s.holes ?? []);
39093
39322
  const els = s.elements.filter((_, i) => !holes.has(i));
39094
- let el = anyAbs;
39095
- if (els.length > 0) {
39096
- el = els.map(widenForAssign).reduce((x, y) => joinAbs(x, y));
39097
- } else if (s.rest) {
39098
- el = widenForAssign(s.rest);
39099
- }
39323
+ const parts = els.map(widenForAssign);
39324
+ if (s.rest) parts.push(widenForAssign(s.rest));
39325
+ const el = parts.length > 0 ? parts.reduce((x, y) => joinAbs(x, y)) : anyAbs;
39100
39326
  return abs({ k: "arr", element: el }, void 0, void 0, a.conf);
39101
39327
  }
39102
39328
  if (s.k === "obj") {
@@ -40641,6 +40867,7 @@ __export(src_exports, {
40641
40867
  interfaceSourceOf: () => interfaceSourceOf,
40642
40868
  interfaceTierOf: () => interfaceTierOf,
40643
40869
  isAbsApplyResult: () => isAbsApplyResult,
40870
+ isAbsVal: () => isAbsVal,
40644
40871
  isArrMutator: () => isArrMutator,
40645
40872
  isBigPrim: () => isBigPrim,
40646
40873
  isDefinitelyFalse: () => isDefinitelyFalse,
@@ -41046,6 +41273,7 @@ function paramName(param) {
41046
41273
  return void 0;
41047
41274
  }
41048
41275
  function unwrapDefaultExport(mod2) {
41276
+ if (mod2 == null) return void 0;
41049
41277
  if (typeof mod2 === "function") return mod2;
41050
41278
  return mod2.default;
41051
41279
  }
@@ -41111,9 +41339,73 @@ function emitDirectiveDiag(d) {
41111
41339
  function resetDirectiveDiagSeen() {
41112
41340
  directiveDiagSeen.clear();
41113
41341
  }
41342
+ function isSafeTypeExprNode(node) {
41343
+ switch (node.type) {
41344
+ case "NumericLiteral":
41345
+ case "StringLiteral":
41346
+ case "BooleanLiteral":
41347
+ case "NullLiteral":
41348
+ return true;
41349
+ case "Identifier":
41350
+ return node.name === "undefined";
41351
+ case "UnaryExpression":
41352
+ return node.operator === "-" && node.argument.type === "NumericLiteral";
41353
+ case "ArrayExpression":
41354
+ return node.elements.every(
41355
+ (el) => el !== null && el.type !== "SpreadElement" && isSafeTypeExprNode(el)
41356
+ );
41357
+ case "ObjectExpression":
41358
+ return node.properties.every((prop) => {
41359
+ if (prop.type !== "ObjectProperty") return false;
41360
+ if (prop.computed) return false;
41361
+ const key = prop.key;
41362
+ if (key.type !== "Identifier" && key.type !== "StringLiteral") return false;
41363
+ return isSafeTypeExprNode(prop.value);
41364
+ });
41365
+ case "CallExpression": {
41366
+ const argsOk = node.arguments.every(
41367
+ (arg) => arg.type !== "SpreadElement" && arg.type !== "ArgumentPlaceholder" && isSafeTypeExprNode(arg)
41368
+ );
41369
+ if (!argsOk) return false;
41370
+ return isSafeTypeExprCallee(node.callee);
41371
+ }
41372
+ default:
41373
+ return false;
41374
+ }
41375
+ }
41376
+ function isSafeTypeExprCallee(node) {
41377
+ if (node.type === "Identifier") {
41378
+ return CONSTRAINT_BUILDER_NAMES.includes(node.name);
41379
+ }
41380
+ if (node.type === "MemberExpression") {
41381
+ if (node.computed) return false;
41382
+ if (node.property.type !== "Identifier") return false;
41383
+ if (!TYPE_EXPR_CHAIN_METHODS.has(node.property.name)) return false;
41384
+ return isSafeTypeExprNode(node.object);
41385
+ }
41386
+ return false;
41387
+ }
41388
+ function isSafeTypeExprSource(s) {
41389
+ try {
41390
+ const ast = parse2(`(${s});`);
41391
+ if (ast.program.body.length !== 1) return false;
41392
+ const stmt = ast.program.body[0];
41393
+ if (stmt.type !== "ExpressionStatement") return false;
41394
+ return isSafeTypeExprNode(stmt.expression);
41395
+ } catch {
41396
+ return false;
41397
+ }
41398
+ }
41114
41399
  function tryParseConstraint(expr) {
41115
41400
  const s = expr.trim();
41116
41401
  if (!CONSTRAINT_EXPR_RE.test(s)) return void 0;
41402
+ if (!isSafeTypeExprSource(s)) {
41403
+ emitDirectiveDiag({
41404
+ code: "nudo:directive-syntax",
41405
+ message: `Unsafe constraint expression rejected: ${s.slice(0, 80)} (only builder calls, chain methods, and literals are allowed)`
41406
+ });
41407
+ return void 0;
41408
+ }
41117
41409
  try {
41118
41410
  const src = `export const __nudo_case_arg = (${s});`;
41119
41411
  const exports = execNudoModule(src);
@@ -41149,9 +41441,10 @@ function constraintSelfEqLit(c, selfIds) {
41149
41441
  if (isSelf(b) && a.op === "lit") return { found: true, value: a.value };
41150
41442
  return { found: false };
41151
41443
  }
41152
- function constraintToCaseArgAbs(c) {
41444
+ function constraintToCaseArgAbs(c, depth) {
41445
+ if (depth > MAX_CASE_ARG_DEPTH) return depthCapDiag(constraintPreview(c));
41153
41446
  if (c.members && c.members.length > 0) {
41154
- const parts = c.members.map((m) => constraintToCaseArgAbs(m));
41447
+ const parts = c.members.map((m) => constraintToCaseArgAbs(m, depth + 1));
41155
41448
  if (parts.length === 1) return parts[0];
41156
41449
  return { shape: { k: "sum", members: parts }, conf: "path" };
41157
41450
  }
@@ -41166,12 +41459,12 @@ function constraintToCaseArgAbs(c) {
41166
41459
  return absUndefLit();
41167
41460
  }
41168
41461
  if (c.element) {
41169
- return absExact({ k: "arr", element: constraintToCaseArgAbs(c.element) });
41462
+ return absExact({ k: "arr", element: constraintToCaseArgAbs(c.element, depth + 1) });
41170
41463
  }
41171
41464
  if (c.fields) {
41172
41465
  const slots = {};
41173
41466
  for (const [key, field] of Object.entries(c.fields)) {
41174
- slots[key] = { value: constraintToCaseArgAbs(field.constraint) };
41467
+ slots[key] = { value: constraintToCaseArgAbs(field.constraint, depth + 1) };
41175
41468
  }
41176
41469
  return absExact({ k: "obj", slots });
41177
41470
  }
@@ -41184,18 +41477,33 @@ function constraintToCaseArgAbs(c) {
41184
41477
  }
41185
41478
  return constraintToEntryAbs(c, "__arg");
41186
41479
  }
41187
- function parseCaseArgExpr(expr) {
41480
+ function depthCapDiag(expr) {
41481
+ emitDirectiveDiag({
41482
+ code: "nudo:directive-syntax",
41483
+ message: `Type expression nesting exceeds depth ${MAX_CASE_ARG_DEPTH}: ${expr.trim().slice(0, 40)} \u2026 (deeper structure collapses to unknown)`
41484
+ });
41485
+ return absUnknown2();
41486
+ }
41487
+ function constraintPreview(c) {
41488
+ if (c.element) return "array(\u2026)";
41489
+ if (c.members) return "union(\u2026)";
41490
+ if (c.fields) return "shape({ \u2026 })";
41491
+ if (c.fn) return "fn(\u2026)";
41492
+ return c.prim ? `${c.prim}()` : "constraint";
41493
+ }
41494
+ function parseCaseArgExpr(expr, depth = 0) {
41495
+ if (depth > MAX_CASE_ARG_DEPTH) return depthCapDiag(expr);
41188
41496
  const constraint = tryParseConstraint(expr);
41189
41497
  if (constraint) {
41190
41498
  try {
41191
- return constraintToCaseArgAbs(constraint);
41499
+ return constraintToCaseArgAbs(constraint, depth);
41192
41500
  } catch {
41193
41501
  return absUnknown2();
41194
41502
  }
41195
41503
  }
41196
- return parseLiteralOrStructure(expr);
41504
+ return parseLiteralOrStructure(expr, depth);
41197
41505
  }
41198
- function parseLiteralOrStructure(expr) {
41506
+ function parseLiteralOrStructure(expr, depth) {
41199
41507
  const s = expr.trim();
41200
41508
  if (s === "true") return absLit(true);
41201
41509
  if (s === "false") return absLit(false);
@@ -41214,17 +41522,20 @@ function parseLiteralOrStructure(expr) {
41214
41522
  return absLit(s.slice(1, -1));
41215
41523
  }
41216
41524
  if (s.startsWith("{") && s.endsWith("}")) {
41217
- return parseObjectLiteral(s.slice(1, -1).trim());
41525
+ return parseObjectLiteral(s.slice(1, -1).trim(), depth);
41218
41526
  }
41219
41527
  if (s.startsWith("[") && s.endsWith("]")) {
41220
41528
  const content = s.slice(1, -1).trim();
41221
41529
  if (!content) return absExact({ k: "tuple", elements: [] });
41222
41530
  const elements = splitTopLevelArgs(content);
41223
- return absExact({ k: "tuple", elements: elements.map((e) => parseCaseArgExpr(e)) });
41531
+ return absExact({
41532
+ k: "tuple",
41533
+ elements: elements.map((e) => parseCaseArgExpr(e, depth + 1))
41534
+ });
41224
41535
  }
41225
41536
  return absUnknown2();
41226
41537
  }
41227
- function parseObjectLiteral(content) {
41538
+ function parseObjectLiteral(content, depth) {
41228
41539
  if (!content) return absExact({ k: "obj", slots: {} });
41229
41540
  const entries = splitTopLevelArgs(content);
41230
41541
  const slots = {};
@@ -41233,7 +41544,7 @@ function parseObjectLiteral(content) {
41233
41544
  if (colonIdx === -1) continue;
41234
41545
  const key = entry.slice(0, colonIdx).trim().replace(/^["']|["']$/g, "");
41235
41546
  const val = entry.slice(colonIdx + 1).trim();
41236
- slots[key] = { value: parseCaseArgExpr(val) };
41547
+ slots[key] = { value: parseCaseArgExpr(val, depth + 1) };
41237
41548
  }
41238
41549
  return absExact({ k: "obj", slots });
41239
41550
  }
@@ -41399,6 +41710,13 @@ function parseSkipReturnsExpr(rest) {
41399
41710
  if (inner !== null && s.slice(inner.length + 2).trim() === "") {
41400
41711
  return parseAbsExprDiag(inner, "@nudo:skip (...)");
41401
41712
  }
41713
+ if (inner === null) {
41714
+ emitDirectiveDiag({
41715
+ code: "nudo:directive-syntax",
41716
+ message: `@nudo:skip type expression has unclosed parenthesis (expected @nudo:skip (<typeExpr>)) \u2014 got: ${s.slice(0, 60)}`
41717
+ });
41718
+ return void 0;
41719
+ }
41402
41720
  }
41403
41721
  return void 0;
41404
41722
  }
@@ -41492,7 +41810,8 @@ function parseSinonExpr(expr) {
41492
41810
  return null;
41493
41811
  }
41494
41812
  function parseNudoMockExpr(expr) {
41495
- const s = expr.trim();
41813
+ let s = expr.trim();
41814
+ while (s.startsWith("sinon.")) s = s.slice("sinon.".length);
41496
41815
  const stubReturnsOnFirstMatch = s.match(/^stub\(\)\.returns\((.+)\)\.onFirstCall\(\)$/);
41497
41816
  if (stubReturnsOnFirstMatch) {
41498
41817
  const helper = stub.returns(parseAbsExpr(stubReturnsOnFirstMatch[1].trim()));
@@ -41563,9 +41882,6 @@ function parseNudoMockExpr(expr) {
41563
41882
  if (s === "mock()") {
41564
41883
  return mock();
41565
41884
  }
41566
- if (s.startsWith("sinon.")) {
41567
- return parseNudoMockExpr(s.slice("sinon.".length));
41568
- }
41569
41885
  return null;
41570
41886
  }
41571
41887
  function parseDirectivesFromCommentTexts(comments) {
@@ -41675,8 +41991,17 @@ function scanMalformedCaseNames(text, caseArgSpans) {
41675
41991
  while ((m = CASE_TAG_REGEX.exec(text)) !== null) {
41676
41992
  if (inCaseArgs(m.index)) continue;
41677
41993
  const rest = m[1].trim();
41678
- if (/^"[^"]+"\s*\(/.test(rest)) continue;
41679
- if (/^"[^"]+"\s*\(/.test(rest)) continue;
41994
+ const legal = rest.match(/^("[^"]+")[ \t]*\(/);
41995
+ if (legal) {
41996
+ const parenIdx = m.index + m[0].length - m[1].length + legal[0].length - 1;
41997
+ if (extractBalancedParens(text, parenIdx) === null) {
41998
+ emitDirectiveDiag({
41999
+ code: "nudo:directive-syntax",
42000
+ message: `Malformed @nudo:case: unclosed argument list (missing closing ")") \u2014 got: @nudo:case ${rest.slice(0, 60)}`
42001
+ });
42002
+ }
42003
+ continue;
42004
+ }
41680
42005
  let reason;
41681
42006
  if (rest.startsWith("'")) {
41682
42007
  reason = `single-quoted name (use double quotes: @nudo:case "name" (\u2026))`;
@@ -41716,6 +42041,14 @@ function scanMalformedMockNames(text, caseArgSpans) {
41716
42041
  code: "nudo:directive-syntax",
41717
42042
  message: `Malformed @nudo:mock name '${name}': not a valid identifier (use letters, digits, _, $; Unicode letters allowed)`
41718
42043
  });
42044
+ continue;
42045
+ }
42046
+ const afterName = rest.slice(name.length);
42047
+ if (/^[ \t]+from[ \t]+"[^"\n]*$/.test(afterName)) {
42048
+ emitDirectiveDiag({
42049
+ code: "nudo:directive-syntax",
42050
+ message: `Malformed @nudo:mock from: unclosed quote in path (expected @nudo:mock ${name} from "path") \u2014 got: @nudo:mock ${rest.slice(0, 60)}`
42051
+ });
41719
42052
  }
41720
42053
  }
41721
42054
  }
@@ -41742,6 +42075,28 @@ function extractDirectivesQuiet(ast) {
41742
42075
  takeDirectiveDiagsSince(since);
41743
42076
  return out;
41744
42077
  }
42078
+ function stripInlineCommentPrefix(line, kind) {
42079
+ const s = kind === "block" ? line.replace(/^\s*\*\s?/, "") : line.replace(/^\//, "");
42080
+ return s.trim();
42081
+ }
42082
+ function findTrailingCommentStart(expr) {
42083
+ let inString = null;
42084
+ for (let i = 0; i < expr.length; i++) {
42085
+ const ch = expr[i];
42086
+ if (inString) {
42087
+ if (ch === inString) inString = null;
42088
+ continue;
42089
+ }
42090
+ if (ch === '"' || ch === "'") {
42091
+ inString = ch;
42092
+ continue;
42093
+ }
42094
+ if ((ch === " " || ch === " ") && /^[ \t]+(?:\/\/|\/\*)/.test(expr.slice(i))) {
42095
+ return i;
42096
+ }
42097
+ }
42098
+ return -1;
42099
+ }
41745
42100
  function extractInlineDirectives(node) {
41746
42101
  resetDirectiveDiagSeen();
41747
42102
  const comments = node.leadingComments;
@@ -41749,39 +42104,56 @@ function extractInlineDirectives(node) {
41749
42104
  const results = [];
41750
42105
  for (const comment of comments) {
41751
42106
  if (comment.type !== "CommentLine" && comment.type !== "CommentBlock") continue;
41752
- const text = comment.value;
41753
- const asMatch = text.match(AS_REGEX);
41754
- if (asMatch) {
41755
- const rawExpr = asMatch[1].trim();
41756
- const trailing = rawExpr.match(AS_TRAILING_COMMENT_RE);
41757
- if (trailing) {
41758
- const typePart = rawExpr.slice(0, trailing.index).trim();
42107
+ const kind = comment.type === "CommentLine" ? "line" : "block";
42108
+ for (const rawLine of comment.value.split("\n")) {
42109
+ const line = stripInlineCommentPrefix(rawLine, kind);
42110
+ const asMatch = line.match(AS_LINE_REGEX);
42111
+ if (asMatch) {
42112
+ const rawExpr = asMatch[1].trim();
42113
+ const trailingIdx = findTrailingCommentStart(rawExpr);
42114
+ if (trailingIdx !== -1) {
42115
+ const typePart = rawExpr.slice(0, trailingIdx).trim();
42116
+ emitDirectiveDiag({
42117
+ code: "nudo:directive-syntax",
42118
+ message: `Trailing comment in @nudo:as type expression: '${rawExpr}' (remove the comment; the type must be a clean expression)`
42119
+ });
42120
+ results.push({ kind: "as", typeAbs: typePart ? parseAbsExprDiag(typePart, "@nudo:as") : absUnknown2() });
42121
+ continue;
42122
+ }
42123
+ results.push({ kind: "as", typeAbs: parseAbsExprDiag(rawExpr, "@nudo:as") });
42124
+ continue;
42125
+ }
42126
+ if (AS_BARE_RE.test(line)) {
41759
42127
  emitDirectiveDiag({
41760
42128
  code: "nudo:directive-syntax",
41761
- message: `Trailing comment in @nudo:as type expression: '${rawExpr}' (remove the comment; the type must be a clean expression)`
42129
+ message: `@nudo:as requires a type expression (e.g. @nudo:as number())`
41762
42130
  });
41763
- results.push({ kind: "as", typeAbs: typePart ? parseAbsExprDiag(typePart, "@nudo:as") : absUnknown2() });
41764
42131
  continue;
41765
42132
  }
41766
- results.push({ kind: "as", typeAbs: parseAbsExprDiag(rawExpr, "@nudo:as") });
41767
- continue;
41768
- }
41769
- const replaceMatch = text.match(REPLACE_REGEX);
41770
- if (replaceMatch) {
41771
- const raw = replaceMatch[1].trim();
41772
- const sepIdx = findReplaceSeparator(raw);
41773
- if (sepIdx !== -1) {
41774
- const targetSource = raw.slice(0, sepIdx).trim();
41775
- const typeExprStr = raw.slice(sepIdx + 1).trim();
41776
- results.push({
41777
- kind: "replace",
41778
- targetSource,
41779
- typeAbs: parseAbsExprDiag(typeExprStr, "@nudo:replace")
41780
- });
41781
- } else {
42133
+ const replaceMatch = line.match(REPLACE_LINE_REGEX);
42134
+ if (replaceMatch) {
42135
+ const raw = replaceMatch[1].trim();
42136
+ const sepIdx = findReplaceSeparator(raw);
42137
+ if (sepIdx !== -1) {
42138
+ const targetSource = raw.slice(0, sepIdx).trim();
42139
+ const typeExprStr = raw.slice(sepIdx + 1).trim();
42140
+ results.push({
42141
+ kind: "replace",
42142
+ targetSource,
42143
+ typeAbs: parseAbsExprDiag(typeExprStr, "@nudo:replace")
42144
+ });
42145
+ } else {
42146
+ emitDirectiveDiag({
42147
+ code: "nudo:directive-syntax",
42148
+ message: `Malformed @nudo:replace: cannot separate target from type (expected @nudo:replace <target> <typeExpr>) \u2014 got: ${raw.slice(0, 60)}`
42149
+ });
42150
+ }
42151
+ continue;
42152
+ }
42153
+ if (REPLACE_BARE_RE.test(line)) {
41782
42154
  emitDirectiveDiag({
41783
42155
  code: "nudo:directive-syntax",
41784
- message: `Malformed @nudo:replace: cannot separate target from type (expected @nudo:replace <target> <typeExpr>) \u2014 got: ${raw.slice(0, 60)}`
42156
+ message: `@nudo:replace requires a target and type expression (expected @nudo:replace <target> <typeExpr>)`
41785
42157
  });
41786
42158
  }
41787
42159
  }
@@ -41846,7 +42218,7 @@ function extractFileDirectives(ast) {
41846
42218
  }
41847
42219
  return results;
41848
42220
  }
41849
- var directiveDiagCollector, directiveDiagSeq, directiveDiags, MAX_DIRECTIVE_DIAGS, directiveDiagSeen, CASE_TAG_REGEX, JS_IDENT_RE, MOCK_INLINE_REGEX, MOCK_FROM_REGEX, MOCK_TAG_REGEX, PURE_REGEX, SKIP_REGEX, SAMPLE_REGEX, SAMPLE_NUM_RE, AS_REGEX, REPLACE_REGEX, AS_TRAILING_COMMENT_RE, ENV_REGEX, MOCK_MODULE_REGEX;
42221
+ var directiveDiagCollector, directiveDiagSeq, directiveDiags, MAX_DIRECTIVE_DIAGS, directiveDiagSeen, CASE_TAG_REGEX, JS_IDENT_RE, MOCK_INLINE_REGEX, MOCK_FROM_REGEX, MOCK_TAG_REGEX, PURE_REGEX, SKIP_REGEX, SAMPLE_REGEX, SAMPLE_NUM_RE, TYPE_EXPR_CHAIN_METHODS, MAX_CASE_ARG_DEPTH, AS_LINE_REGEX, REPLACE_LINE_REGEX, AS_BARE_RE, REPLACE_BARE_RE, ENV_REGEX, MOCK_MODULE_REGEX;
41850
42222
  var init_directives = __esm({
41851
42223
  "../parser/src/directives.ts"() {
41852
42224
  "use strict";
@@ -41857,18 +42229,32 @@ var init_directives = __esm({
41857
42229
  directiveDiags = [];
41858
42230
  MAX_DIRECTIVE_DIAGS = 1024;
41859
42231
  directiveDiagSeen = /* @__PURE__ */ new Set();
41860
- CASE_TAG_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*)?@nudo:case\s+([^\n]+)/g;
42232
+ CASE_TAG_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*|\/[ \t]*)?@nudo:case\b[ \t]*([^\n]*)/g;
41861
42233
  JS_IDENT_RE = /^[\p{L}$_][\p{L}\p{N}$_]*$/u;
41862
- MOCK_INLINE_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*)?@nudo:mock\s+([^\s=]+)\s*=\s*(.+)/g;
41863
- MOCK_FROM_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*)?@nudo:mock\s+([^\s]+)\s+from\s+"([^"]+)"/g;
41864
- MOCK_TAG_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*)?@nudo:mock\s+([^\n]+)/g;
41865
- PURE_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*)?@nudo:pure\b/g;
41866
- SKIP_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*)?@nudo:skip\b(?:[ \t]+(\S[^\n]*))?/g;
41867
- SAMPLE_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*)?@nudo:sample\b(?:[ \t]+(\S+))?/g;
42234
+ MOCK_INLINE_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*|\/[ \t]*)?@nudo:mock\b[ \t]+([^\s=]+)[ \t]*=[ \t]*(.+)/g;
42235
+ MOCK_FROM_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*|\/[ \t]*)?@nudo:mock\b[ \t]+([^\s]+)[ \t]+from[ \t]+"([^"\n]+)"/g;
42236
+ MOCK_TAG_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*|\/[ \t]*)?@nudo:mock\b[ \t]*([^\n]*)/g;
42237
+ PURE_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*|\/[ \t]*)?@nudo:pure\b/g;
42238
+ SKIP_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*|\/[ \t]*)?@nudo:skip\b(?:[ \t]+(\S[^\n]*))?/g;
42239
+ SAMPLE_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*|\/[ \t]*)?@nudo:sample\b(?:[ \t]+(\S+))?/g;
41868
42240
  SAMPLE_NUM_RE = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/;
41869
- AS_REGEX = /^\s*@nudo:as\s+(.+)/;
41870
- REPLACE_REGEX = /^\s*@nudo:replace\s+(.+)/;
41871
- AS_TRAILING_COMMENT_RE = /\s+(?:\/\/|\/\*)/;
42241
+ TYPE_EXPR_CHAIN_METHODS = /* @__PURE__ */ new Set([
42242
+ "gt",
42243
+ "ge",
42244
+ "lt",
42245
+ "le",
42246
+ "int",
42247
+ "min",
42248
+ "max",
42249
+ "length",
42250
+ "shift",
42251
+ "optional"
42252
+ ]);
42253
+ MAX_CASE_ARG_DEPTH = 32;
42254
+ AS_LINE_REGEX = /^@nudo:as[ \t]+(.+)$/;
42255
+ REPLACE_LINE_REGEX = /^@nudo:replace[ \t]+(.+)$/;
42256
+ AS_BARE_RE = /^@nudo:as[ \t]*$/;
42257
+ REPLACE_BARE_RE = /^@nudo:replace[ \t]*$/;
41872
42258
  ENV_REGEX = /^\/?\s*@nudo:env\s+(.+)/;
41873
42259
  MOCK_MODULE_REGEX = /^\/?\s*@nudo:mock-module\s+(.+)/;
41874
42260
  }
@@ -42112,6 +42498,27 @@ var init_inlay = __esm({
42112
42498
  function isJsIdent(name) {
42113
42499
  return JS_IDENT.test(name);
42114
42500
  }
42501
+ function isJsBindingIdent(name) {
42502
+ return JS_BINDING_IDENT.test(name) && !JS_BINDING_RESERVED.has(name);
42503
+ }
42504
+ function toJsBindingIdent(name, used) {
42505
+ let base = name;
42506
+ if (!JS_BINDING_IDENT.test(base)) {
42507
+ base = base.replace(/[^\p{ID_Continue}$\u200C\u200D]/gu, "_");
42508
+ if (base.length === 0) base = "_";
42509
+ else if (!/^[$_\p{ID_Start}]/u.test(base)) base = `_${base}`;
42510
+ }
42511
+ if (JS_BINDING_RESERVED.has(base)) base = `_${base}`;
42512
+ if (!used) return base;
42513
+ let n = base;
42514
+ let i = 2;
42515
+ while (used.has(n)) {
42516
+ n = `${base}_${i}`;
42517
+ i++;
42518
+ }
42519
+ used.add(n);
42520
+ return n;
42521
+ }
42115
42522
  function formatObjectKey(k) {
42116
42523
  if (JS_IDENT.test(k)) return k;
42117
42524
  if (JS_CANONICAL_INT.test(k)) return k;
@@ -42126,27 +42533,90 @@ function escapeTemplateTypeFixed(text) {
42126
42533
  function sanitizeCommentText(text) {
42127
42534
  return text.replace(/\*\//g, "*\\/").replace(/[\r\n\u2028\u2029\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, " ");
42128
42535
  }
42129
- var JS_IDENT, JS_CANONICAL_INT;
42536
+ var JS_IDENT, JS_BINDING_IDENT, JS_BINDING_RESERVED, JS_CANONICAL_INT;
42130
42537
  var init_codegen_escape = __esm({
42131
42538
  "../core/src/algebra/codegen-escape.ts"() {
42132
42539
  "use strict";
42133
42540
  JS_IDENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
42541
+ JS_BINDING_IDENT = /^(?:[$_\p{ID_Start}])(?:[$\u200C\u200D\p{ID_Continue}])*$/u;
42542
+ JS_BINDING_RESERVED = /* @__PURE__ */ new Set([
42543
+ // ECMA-262 keywords
42544
+ "break",
42545
+ "case",
42546
+ "catch",
42547
+ "class",
42548
+ "const",
42549
+ "continue",
42550
+ "debugger",
42551
+ "default",
42552
+ "delete",
42553
+ "do",
42554
+ "else",
42555
+ "enum",
42556
+ "export",
42557
+ "extends",
42558
+ "false",
42559
+ "finally",
42560
+ "for",
42561
+ "function",
42562
+ "if",
42563
+ "import",
42564
+ "in",
42565
+ "instanceof",
42566
+ "new",
42567
+ "null",
42568
+ "return",
42569
+ "super",
42570
+ "switch",
42571
+ "this",
42572
+ "throw",
42573
+ "true",
42574
+ "try",
42575
+ "typeof",
42576
+ "var",
42577
+ "void",
42578
+ "while",
42579
+ "with",
42580
+ "yield",
42581
+ // strict mode / module-code reserved
42582
+ "let",
42583
+ "static",
42584
+ "await",
42585
+ "implements",
42586
+ "interface",
42587
+ "package",
42588
+ "private",
42589
+ "protected",
42590
+ "public",
42591
+ // never legal bindings in strict / module code
42592
+ "eval",
42593
+ "arguments"
42594
+ ]);
42134
42595
  JS_CANONICAL_INT = /^(?:0|[1-9]\d*)$/;
42135
42596
  }
42136
42597
  });
42137
42598
 
42138
42599
  // ../core/src/algebra/denote.ts
42139
42600
  function denoteGuard(a, v2) {
42140
- if (a.term?.op === "lit" && (!a.pred || a.pred.op === "true")) {
42141
- return eqGuard(v2, a.term.value);
42601
+ return denoteGuardB(a, v2, new ProjectionBudget());
42602
+ }
42603
+ function denoteGuardB(a, v2, budget) {
42604
+ const stop2 = budget.enter(a);
42605
+ if (stop2) return `/* nudo:truncated:${stop2} */ true`;
42606
+ try {
42607
+ if (a.term?.op === "lit" && (!a.pred || a.pred.op === "true")) {
42608
+ return eqGuard(v2, a.term.value);
42609
+ }
42610
+ const shape2 = denoteShape(a.shape, v2, budget);
42611
+ const pred = denotePred(a, v2);
42612
+ if (shape2 === "true") return pred;
42613
+ if (pred === "true") return shape2;
42614
+ return `(${shape2}) && (${pred})`;
42615
+ } finally {
42616
+ budget.exit();
42142
42617
  }
42143
- const shape2 = denoteShape(a.shape, v2);
42144
- const pred = denotePred(a, v2);
42145
- if (shape2 === "true") return pred;
42146
- if (pred === "true") return shape2;
42147
- return `(${shape2}) && (${pred})`;
42148
42618
  }
42149
- function denoteShape(s, v2) {
42619
+ function denoteShape(s, v2, budget) {
42150
42620
  switch (s.k) {
42151
42621
  case "never":
42152
42622
  return "false";
@@ -42172,7 +42642,7 @@ function denoteShape(s, v2) {
42172
42642
  const checks = [`typeof ${v2} === "object"`, `${v2} !== null`];
42173
42643
  for (const [key, slot] of Object.entries(s.slots)) {
42174
42644
  const access = safeMemberAccess(v2, key);
42175
- const inner = denoteGuard(slot.value, access);
42645
+ const inner = denoteGuardB(slot.value, access, budget);
42176
42646
  if (slot.optional) {
42177
42647
  if (inner !== "true") checks.push(`(${access} === undefined || ${inner})`);
42178
42648
  } else {
@@ -42182,7 +42652,7 @@ function denoteShape(s, v2) {
42182
42652
  return checks.join(" && ");
42183
42653
  }
42184
42654
  case "arr":
42185
- return `Array.isArray(${v2}) && ${v2}.every((item) => ${denoteGuard(s.element, "item")})`;
42655
+ return `Array.isArray(${v2}) && ${v2}.every((item) => ${denoteGuardB(s.element, "item", budget)})`;
42186
42656
  case "tuple": {
42187
42657
  const checks = [`Array.isArray(${v2})`];
42188
42658
  const minLen = s.elements.length;
@@ -42193,14 +42663,14 @@ function denoteShape(s, v2) {
42193
42663
  checks.push(`!(${i} in ${v2})`);
42194
42664
  return;
42195
42665
  }
42196
- const inner = denoteGuard(el, `${v2}[${i}]`);
42666
+ const inner = denoteGuardB(el, `${v2}[${i}]`, budget);
42197
42667
  if (inner !== "true") checks.push(inner);
42198
42668
  });
42199
42669
  for (const i of holes) {
42200
42670
  if (i >= minLen) checks.push(`!(${i} in ${v2})`);
42201
42671
  }
42202
42672
  if (s.rest) {
42203
- const rest = denoteGuard(s.rest, "item");
42673
+ const rest = denoteGuardB(s.rest, "item", budget);
42204
42674
  if (rest !== "true") {
42205
42675
  checks.push(`${v2}.slice(${minLen}).every((item) => ${rest})`);
42206
42676
  }
@@ -42213,10 +42683,10 @@ function denoteShape(s, v2) {
42213
42683
  if (s.eff === "promise") return `${v2} instanceof Promise`;
42214
42684
  return "true";
42215
42685
  case "brand":
42216
- return denoteShape(s.shape.shape, v2);
42686
+ return denoteShape(s.shape.shape, v2, budget);
42217
42687
  case "sum": {
42218
42688
  if (s.members.length === 0) return "false";
42219
- const parts = s.members.map((m) => denoteGuard(m, v2));
42689
+ const parts = s.members.map((m) => denoteGuardB(m, v2, budget));
42220
42690
  return `(${parts.join(" || ")})`;
42221
42691
  }
42222
42692
  }
@@ -42310,6 +42780,7 @@ var init_denote = __esm({
42310
42780
  "../core/src/algebra/denote.ts"() {
42311
42781
  "use strict";
42312
42782
  init_codegen_escape();
42783
+ init_projection_budget();
42313
42784
  }
42314
42785
  });
42315
42786
 
@@ -42327,8 +42798,10 @@ __export(internal_exports, {
42327
42798
  MAX_PROMISE_MICROS: () => MAX_PROMISE_MICROS,
42328
42799
  MAX_TOTAL_CALLS: () => MAX_TOTAL_CALLS,
42329
42800
  OBJECT_PROTO_NAMES: () => OBJECT_PROTO_NAMES,
42801
+ PROJECTION_MAX_DEPTH: () => PROJECTION_MAX_DEPTH,
42330
42802
  PROMISE_MICRO_ERROR_LABEL: () => PROMISE_MICRO_ERROR_LABEL,
42331
42803
  PROMISE_MICRO_OVERFLOW_LABEL: () => PROMISE_MICRO_OVERFLOW_LABEL,
42804
+ ProjectionBudget: () => ProjectionBudget,
42332
42805
  abortDerivationSession: () => abortDerivationSession,
42333
42806
  absTemplateViews: () => absTemplateViews,
42334
42807
  allFixedTextOfViews: () => allFixedTextOfViews,
@@ -42363,6 +42836,7 @@ __export(internal_exports, {
42363
42836
  exceedsBudget: () => exceedsBudget,
42364
42837
  exitCall: () => exitCall,
42365
42838
  exitEvalCallBudgetSession: () => exitEvalCallBudgetSession,
42839
+ exportedNameOfLocal: () => exportedNameOfLocal,
42366
42840
  extractAllLoadSpecs: () => extractAllLoadSpecs,
42367
42841
  filterDeclaredThrows: () => filterDeclaredThrows,
42368
42842
  filterGateThrows: () => filterGateThrows,
@@ -42391,6 +42865,7 @@ __export(internal_exports, {
42391
42865
  instanceOf: () => instanceOf,
42392
42866
  instantiateClass: () => instantiateClass,
42393
42867
  isEvalMissingSlotEnabled: () => isEvalMissingSlotEnabled,
42868
+ isJsBindingIdent: () => isJsBindingIdent,
42394
42869
  isJsIdent: () => isJsIdent,
42395
42870
  isNullishAbs: () => isNullishAbs,
42396
42871
  isTemplateLike: () => isTemplateLike,
@@ -42467,6 +42942,7 @@ __export(internal_exports, {
42467
42942
  termNodes: () => termNodes,
42468
42943
  throwAbsToKinds: () => throwAbsToKinds,
42469
42944
  throwsKindCovered: () => throwsKindCovered,
42945
+ toJsBindingIdent: () => toJsBindingIdent,
42470
42946
  truncatedAbs: () => truncatedAbs,
42471
42947
  viewTemplateParts: () => viewTemplateParts,
42472
42948
  withEvalBudgetOverride: () => withEvalBudgetOverride,
@@ -42478,6 +42954,7 @@ var init_internal = __esm({
42478
42954
  "use strict";
42479
42955
  init_code_text();
42480
42956
  init_leak();
42957
+ init_projection_budget();
42481
42958
  init_call_budget();
42482
42959
  init_promise();
42483
42960
  init_calls();
@@ -42486,6 +42963,7 @@ var init_internal = __esm({
42486
42963
  init_stable_source_key();
42487
42964
  init_fn_fp();
42488
42965
  init_load_deps_fp();
42966
+ init_interface();
42489
42967
  init_derivation();
42490
42968
  init_inlay();
42491
42969
  init_template();
@@ -52779,20 +53257,20 @@ function envLimits(env2) {
52779
53257
  };
52780
53258
  }
52781
53259
  function pick2(key, env2, fallback) {
52782
- const e = env2[key];
52783
- if (typeof e === "number" && Number.isFinite(e)) return e;
52784
53260
  const x = explicit[key];
52785
53261
  if (typeof x === "number" && Number.isFinite(x)) return clampEntries(x, fallback);
53262
+ const e = env2[key];
53263
+ if (typeof e === "number" && Number.isFinite(e)) return e;
52786
53264
  const p = fromProject[key];
52787
53265
  if (typeof p === "number" && Number.isFinite(p)) return clampEntries(p, fallback);
52788
53266
  return fallback;
52789
53267
  }
52790
53268
  function getSessionCacheLimits(env2 = process.env) {
52791
- if (envCache === null) envCache = envLimits(env2);
53269
+ const fromEnv = envLimits(env2);
52792
53270
  return {
52793
- maxFiles: pick2("maxFiles", envCache, DEFAULT_SESSION_CACHE_LIMITS.maxFiles),
52794
- maxFns: pick2("maxFns", envCache, DEFAULT_SESSION_CACHE_LIMITS.maxFns),
52795
- maxEvalRuns: pick2("maxEvalRuns", envCache, DEFAULT_SESSION_CACHE_LIMITS.maxEvalRuns)
53271
+ maxFiles: pick2("maxFiles", fromEnv, DEFAULT_SESSION_CACHE_LIMITS.maxFiles),
53272
+ maxFns: pick2("maxFns", fromEnv, DEFAULT_SESSION_CACHE_LIMITS.maxFns),
53273
+ maxEvalRuns: pick2("maxEvalRuns", fromEnv, DEFAULT_SESSION_CACHE_LIMITS.maxEvalRuns)
52796
53274
  };
52797
53275
  }
52798
53276
  function setSessionCacheLimits(partial2) {
@@ -52805,9 +53283,8 @@ function setSessionCacheFromProject(partial2) {
52805
53283
  function resetSessionCacheLimitState() {
52806
53284
  explicit = {};
52807
53285
  fromProject = {};
52808
- envCache = null;
52809
53286
  }
52810
- var DEFAULT_SESSION_CACHE_LIMITS, HARD_CAP, explicit, fromProject, envCache;
53287
+ var DEFAULT_SESSION_CACHE_LIMITS, HARD_CAP, explicit, fromProject;
52811
53288
  var init_session_cache_limits = __esm({
52812
53289
  "../service/src/session-cache-limits.ts"() {
52813
53290
  "use strict";
@@ -52819,7 +53296,6 @@ var init_session_cache_limits = __esm({
52819
53296
  HARD_CAP = 65536;
52820
53297
  explicit = {};
52821
53298
  fromProject = {};
52822
- envCache = null;
52823
53299
  }
52824
53300
  });
52825
53301
 
@@ -52954,15 +53430,22 @@ function findProjectConfig(startDir) {
52954
53430
  while (dir !== root) {
52955
53431
  const pkgPath = resolve6(dir, "package.json");
52956
53432
  if (existsSync7(pkgPath)) {
53433
+ let pkg;
52957
53434
  try {
52958
- const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
52959
- if (pkg.nudo) {
52960
- const nudo = pkg.nudo;
52961
- setSessionCacheFromProject(nudo.sessionCache);
52962
- applyBForkBudgetFromConfig(nudo);
52963
- return { config: nudo, projectDir: dir };
52964
- }
52965
- } catch {
53435
+ pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
53436
+ } catch (err) {
53437
+ process.stderr?.write?.(
53438
+ `nudo: package.json parse failed at ${pkgPath} (${err instanceof Error ? err.message : String(err)}); project config disabled for this subtree (defaults + env apply)
53439
+ `
53440
+ );
53441
+ applyBForkBudgetFromConfig(null);
53442
+ return null;
53443
+ }
53444
+ const nudo = pkg && typeof pkg === "object" ? pkg.nudo : void 0;
53445
+ if (nudo) {
53446
+ setSessionCacheFromProject(nudo.sessionCache);
53447
+ applyBForkBudgetFromConfig(nudo);
53448
+ return { config: nudo, projectDir: dir };
52966
53449
  }
52967
53450
  }
52968
53451
  const parent = dirname5(dir);
@@ -267994,8 +268477,8 @@ function collectExports(sourceFile) {
267994
268477
  }
267995
268478
  return out;
267996
268479
  }
267997
- function draftExportName(name) {
267998
- return name.includes(".") ? name.replace(/\./g, "_") : name;
268480
+ function draftExportName(name, used) {
268481
+ return toJsBindingIdent(name.includes(".") ? name.replace(/\./g, "_") : name, used);
267999
268482
  }
268000
268483
  function formatDtsContractDraft(label, files, exports) {
268001
268484
  const draftable = exports.filter((e) => e.dsl !== void 0);
@@ -268007,6 +268490,7 @@ function formatDtsContractDraft(label, files, exports) {
268007
268490
  }
268008
268491
  if (draftable.some((e) => /\bany\s*\(/.test(e.dsl))) used.add("any");
268009
268492
  const importList = BUILDERS.filter((b) => used.has(b)).join(", ");
268493
+ const exportUsed = /* @__PURE__ */ new Set();
268010
268494
  const lines = [
268011
268495
  "// @nudo:draft",
268012
268496
  `// Generated by \`nudo contract --from-dts\` from ${label}`,
@@ -268033,7 +268517,7 @@ function formatDtsContractDraft(label, files, exports) {
268033
268517
  }
268034
268518
  if (e.note) lines.push(`// ${e.name} \u2014 ${e.note}`);
268035
268519
  else lines.push(`// ${e.name} \u2014 from TypeScript`);
268036
- lines.push(`export const ${draftExportName(e.name)} = ${e.dsl};`);
268520
+ lines.push(`export const ${draftExportName(e.name, exportUsed)} = ${e.dsl};`);
268037
268521
  if (e.name.includes(".")) {
268038
268522
  lines.push(`// sidecar key may also be written as \`${e.name}\``);
268039
268523
  }
@@ -268115,6 +268599,7 @@ var init_dts_contract_draft = __esm({
268115
268599
  "../harvester/src/dts-contract-draft.ts"() {
268116
268600
  "use strict";
268117
268601
  import_typescript2 = __toESM(require_typescript(), 1);
268602
+ init_internal();
268118
268603
  BUILDERS = [
268119
268604
  "fn",
268120
268605
  "number",
@@ -268265,8 +268750,8 @@ function formatHarvestSummary(h) {
268265
268750
  return lines.join("\n");
268266
268751
  }
268267
268752
  function lookupHarvested(h, moduleName, exportName) {
268268
- const mod2 = h.env.modules[moduleName];
268269
- const a = mod2?.[exportName] ?? h.env.globals[exportName];
268753
+ const mod2 = Object.hasOwn(h.env.modules, moduleName) ? h.env.modules[moduleName] : void 0;
268754
+ const a = (mod2 !== void 0 && Object.hasOwn(mod2, exportName) ? mod2[exportName] : void 0) ?? (Object.hasOwn(h.env.globals, exportName) ? h.env.globals[exportName] : void 0);
268270
268755
  if (!a) return void 0;
268271
268756
  return formatShape(a);
268272
268757
  }
@@ -269300,7 +269785,7 @@ function collectRequireSpecs(node, out) {
269300
269785
  };
269301
269786
  visit2(node);
269302
269787
  }
269303
- function buildModulesForFile(source, fromFile, load, evalDep, depth, maxDepth, onIssue) {
269788
+ function buildModulesForFile(source, fromFile, load, evalDep, depth, maxDepth, onIssue, localDepsOut) {
269304
269789
  const modules = {};
269305
269790
  for (const spec of importSpecs(source)) {
269306
269791
  if (spec.startsWith(".") || spec.startsWith("/")) {
@@ -269310,6 +269795,7 @@ function buildModulesForFile(source, fromFile, load, evalDep, depth, maxDepth, o
269310
269795
  continue;
269311
269796
  }
269312
269797
  modules[spec] = evalDep(childPath, spec, fromFile, depth + 1);
269798
+ localDepsOut?.push(stablePathKey(childPath));
269313
269799
  } else if (!spec.startsWith("node:")) {
269314
269800
  const resolved = resolveNpmJsEntryDetailed(spec, dirname10(fromFile));
269315
269801
  if (resolved.exportsUnresolved) {
@@ -269321,6 +269807,7 @@ function buildModulesForFile(source, fromFile, load, evalDep, depth, maxDepth, o
269321
269807
  }
269322
269808
  if (resolved.path) {
269323
269809
  const executed = evalDep(resolved.path, spec, fromFile, depth + 1);
269810
+ localDepsOut?.push(stablePathKey(resolved.path));
269324
269811
  if (executed && (executed.default !== void 0 || Object.keys(executed.named ?? {}).length > 0)) {
269325
269812
  modules[spec] = executed;
269326
269813
  continue;
@@ -269342,6 +269829,18 @@ function clearAbsModuleCache() {
269342
269829
  function evictAbsModuleCacheFiles(paths) {
269343
269830
  for (const p of paths) absModuleCache.delete(stablePathKey(p));
269344
269831
  }
269832
+ function depFingerprintCurrent(deps) {
269833
+ for (const d of deps) {
269834
+ let src;
269835
+ try {
269836
+ src = readFileSync13(d.path, "utf-8");
269837
+ } catch {
269838
+ return false;
269839
+ }
269840
+ if (hashSource(src) !== d.hash) return false;
269841
+ }
269842
+ return true;
269843
+ }
269345
269844
  function moduleLabel(p) {
269346
269845
  const parts = p.split(/[/\\]/);
269347
269846
  return parts[parts.length - 1] || p;
@@ -269426,7 +269925,7 @@ function collectMissingExportIssues(source, modules, fromFile) {
269426
269925
  const checkName = (spec, name, kind) => {
269427
269926
  const mod2 = modules[spec];
269428
269927
  if (!mod2?.evaluated) return;
269429
- const has = name === "default" ? mod2.default !== void 0 : name in mod2.named;
269928
+ const has = name === "default" ? mod2.default !== void 0 : Object.hasOwn(mod2.named, name);
269430
269929
  if (!has) push(spec, name, kind);
269431
269930
  };
269432
269931
  const body = file.program?.body ?? [];
@@ -269459,11 +269958,15 @@ function evalAbsModuleGraph(entrySource, entryFile, opts = {}) {
269459
269958
  const loading = [];
269460
269959
  const issues = [];
269461
269960
  const seenIssue = /* @__PURE__ */ new Set();
269961
+ const issueFlow = [];
269962
+ const depMeta = /* @__PURE__ */ new Map();
269462
269963
  const pushIssue2 = (kind, label, reason) => {
269964
+ const iss = { kind, label, reason };
269965
+ issueFlow.push(iss);
269463
269966
  const key = `${kind}:${label}`;
269464
269967
  if (seenIssue.has(key)) return;
269465
269968
  seenIssue.add(key);
269466
- issues.push({ kind, label, reason });
269969
+ issues.push(iss);
269467
269970
  };
269468
269971
  function evalDep(absPathRaw, spec, fromFile, depth) {
269469
269972
  const absPath = stablePathKey(absPathRaw);
@@ -269481,9 +269984,13 @@ function evalAbsModuleGraph(entrySource, entryFile, opts = {}) {
269481
269984
  if (shared) {
269482
269985
  try {
269483
269986
  const st = statSync9(absPath);
269484
- if (st.mtimeMs === shared.mtimeMs && st.size === shared.size) {
269987
+ if (st.mtimeMs === shared.mtimeMs && st.size === shared.size && depFingerprintCurrent(shared.depFingerprints)) {
269485
269988
  for (const iss of shared.issues) pushIssue2(iss.kind, iss.label, iss.reason);
269486
269989
  cache2.set(absPath, shared.exports);
269990
+ depMeta.set(absPath, {
269991
+ contentHash: shared.contentHash,
269992
+ depFingerprints: shared.depFingerprints
269993
+ });
269487
269994
  return shared.exports;
269488
269995
  }
269489
269996
  } catch {
@@ -269521,7 +270028,8 @@ function evalAbsModuleGraph(entrySource, entryFile, opts = {}) {
269521
270028
  loading.pop();
269522
270029
  return placeholder;
269523
270030
  }
269524
- const issueStart = issues.length;
270031
+ const issueStart = issueFlow.length;
270032
+ const localDeps = [];
269525
270033
  const modules2 = buildModulesForFile(
269526
270034
  source,
269527
270035
  absPath,
@@ -269531,7 +270039,8 @@ function evalAbsModuleGraph(entrySource, entryFile, opts = {}) {
269531
270039
  maxDepth,
269532
270040
  (kind, label, reason) => {
269533
270041
  pushIssue2(kind, label, reason);
269534
- }
270042
+ },
270043
+ localDeps
269535
270044
  );
269536
270045
  let exports;
269537
270046
  const evalRun = tryRunTranspiled(source, { mode: "analyze", modules: modules2 });
@@ -269548,17 +270057,40 @@ function evalAbsModuleGraph(entrySource, entryFile, opts = {}) {
269548
270057
  if (exports.default !== void 0) placeholder.default = exports.default;
269549
270058
  if (exports.evaluated) placeholder.evaluated = true;
269550
270059
  cache2.set(absPath, placeholder);
270060
+ const depFingerprints = [];
270061
+ const seenDep = /* @__PURE__ */ new Set();
270062
+ let untrackable = false;
270063
+ for (const depPath of localDeps) {
270064
+ const meta = depMeta.get(depPath);
270065
+ if (!meta) {
270066
+ untrackable = true;
270067
+ break;
270068
+ }
270069
+ if (!seenDep.has(depPath)) {
270070
+ seenDep.add(depPath);
270071
+ depFingerprints.push({ path: depPath, hash: meta.contentHash });
270072
+ }
270073
+ for (const e of meta.depFingerprints) {
270074
+ if (seenDep.has(e.path)) continue;
270075
+ seenDep.add(e.path);
270076
+ depFingerprints.push(e);
270077
+ }
270078
+ }
270079
+ const subtree = untrackable ? null : { contentHash: hashSource(source), depFingerprints };
270080
+ depMeta.set(absPath, subtree);
269551
270081
  let fingerprint;
269552
270082
  try {
269553
270083
  const st = statSync9(absPath);
269554
270084
  fingerprint = { mtimeMs: st.mtimeMs, size: st.size };
269555
270085
  } catch {
269556
270086
  }
269557
- if (fingerprint) {
270087
+ if (fingerprint && subtree) {
269558
270088
  absModuleCache.set(absPath, {
269559
270089
  ...fingerprint,
270090
+ contentHash: subtree.contentHash,
270091
+ depFingerprints: subtree.depFingerprints,
269560
270092
  exports: placeholder,
269561
- issues: issues.slice(issueStart)
270093
+ issues: issueFlow.slice(issueStart)
269562
270094
  });
269563
270095
  }
269564
270096
  return placeholder;
@@ -269602,7 +270134,7 @@ function importLocalBindings(source, modules) {
269602
270134
  } else if (sp.type === "ImportSpecifier") {
269603
270135
  const imported = sp.imported?.type === "StringLiteral" ? sp.imported.value : sp.imported?.name;
269604
270136
  if (imported === void 0) continue;
269605
- const absVal = imported === "default" ? mod2.default : mod2.named[imported];
270137
+ const absVal = imported === "default" ? mod2.default : Object.hasOwn(mod2.named, imported) ? mod2.named[imported] : void 0;
269606
270138
  out.set(local, absVal ?? unknown);
269607
270139
  }
269608
270140
  }
@@ -269800,7 +270332,7 @@ function pickNamed(mock2, names2) {
269800
270332
  if (mock2.default) def = mock2.default;
269801
270333
  continue;
269802
270334
  }
269803
- const v2 = mock2.named[n];
270335
+ const v2 = Object.hasOwn(mock2.named, n) ? mock2.named[n] : void 0;
269804
270336
  if (v2 !== void 0) named[n] = v2;
269805
270337
  }
269806
270338
  return def !== void 0 ? { named, default: def } : { named };
@@ -269885,6 +270417,7 @@ function trimAnalysisFileCache() {
269885
270417
  }
269886
270418
  }
269887
270419
  function analysisCacheGet(filePath, source, auxKey) {
270420
+ if (getSessionCacheLimits().maxFiles <= 0) return void 0;
269888
270421
  const key = stablePathKey(filePath);
269889
270422
  const e = analysisByFile.get(key);
269890
270423
  if (!e) return void 0;
@@ -269902,6 +270435,7 @@ function analysisCacheSet(filePath, source, auxKey, value) {
269902
270435
  if (oldest === void 0) break;
269903
270436
  analysisByFile.delete(oldest);
269904
270437
  }
270438
+ analysisByFile.delete(key);
269905
270439
  analysisByFile.set(key, { source, auxKey, value });
269906
270440
  }
269907
270441
  function evictAnalysisFileCacheForFiles(files) {
@@ -269943,6 +270477,7 @@ function trimFnAnalysisCache() {
269943
270477
  }
269944
270478
  }
269945
270479
  function fnAnalysisCacheGet(key) {
270480
+ if (getSessionCacheLimits().maxFns <= 0) return void 0;
269946
270481
  const k = normalizeFnCacheKey(key);
269947
270482
  const hit = fnAnalysisCache.get(k);
269948
270483
  if (hit !== void 0) {
@@ -269960,6 +270495,7 @@ function fnAnalysisCacheSet(key, value) {
269960
270495
  if (oldest === void 0) break;
269961
270496
  fnAnalysisCache.delete(oldest);
269962
270497
  }
270498
+ fnAnalysisCache.delete(k);
269963
270499
  fnAnalysisCache.set(k, value);
269964
270500
  }
269965
270501
  function evictFnAnalysisCacheForFiles(files) {
@@ -270150,7 +270686,11 @@ function collectEvalReplacements(source) {
270150
270686
  }
270151
270687
  };
270152
270688
  visitStmts(file.program.body);
270153
- } catch {
270689
+ } catch (err) {
270690
+ if (targets.length > 0 || asTargets.length > 0) {
270691
+ throw err;
270692
+ }
270693
+ noteEvalFallback(err);
270154
270694
  }
270155
270695
  return { targets, values, asTargets, asValues };
270156
270696
  }
@@ -270192,6 +270732,7 @@ function evalCacheSet(filePath, stableSource, mode, envKey, mockKey, depKey, val
270192
270732
  if (oldest === void 0) break;
270193
270733
  evalRunByFile.delete(oldest);
270194
270734
  }
270735
+ evalRunByFile.delete(key);
270195
270736
  evalRunByFile.set(key, { stableSource, mode, envKey, mockKey, depKey, value });
270196
270737
  }
270197
270738
  function trimEvalCache() {
@@ -270210,7 +270751,7 @@ function tryRunEval(source, filePath, opts = {}) {
270210
270751
  const stable = stableAnalyzeKeySource(source);
270211
270752
  const depKey = evalDepKey(source, filePath);
270212
270753
  const canCache = depKey !== null;
270213
- if (canCache) {
270754
+ if (canCache && getSessionCacheLimits().maxEvalRuns > 0) {
270214
270755
  const cacheKey = stablePathKey(filePath);
270215
270756
  const cached2 = evalRunByFile.get(cacheKey);
270216
270757
  if (cached2 && cached2.stableSource === stable && cached2.mode === mode && cached2.envKey === envKey && cached2.mockKey === mockKey && cached2.depKey === depKey) {
@@ -270274,7 +270815,7 @@ function tryRunEval(source, filePath, opts = {}) {
270274
270815
  function tryEvalCallFull(source, filePath, fnName, args, opts = {}) {
270275
270816
  const run = tryRunEval(source, filePath, { envNames: opts.envNames, mocks: opts.mocks });
270276
270817
  if (!run) return void 0;
270277
- if (!(fnName in run.exports)) return void 0;
270818
+ if (!Object.hasOwn(run.exports, fnName)) return void 0;
270278
270819
  const collected = [];
270279
270820
  const memberDiags = [];
270280
270821
  const prevCall = opts.collectCalls ? setEvalCallCollector((r) => collected.push(r)) : void 0;
@@ -270299,7 +270840,14 @@ function tryEvalCall(source, filePath, fnName, args, opts = {}) {
270299
270840
  const full = tryEvalCallFull(source, filePath, fnName, args, opts);
270300
270841
  if (!full) return void 0;
270301
270842
  const r = full.result;
270302
- if (!r) return void 0;
270843
+ if (!isAbsVal(r)) {
270844
+ noteEvalFallback(
270845
+ new Error(
270846
+ `tryEvalCall: '${fnName}' result is not an Abs value (producer contract violation)`
270847
+ )
270848
+ );
270849
+ return void 0;
270850
+ }
270303
270851
  if (r.shape.k === "never" && full.throws.shape.k !== "never") {
270304
270852
  return void 0;
270305
270853
  }
@@ -270502,13 +271050,14 @@ function safeAbsOrUnknown(a) {
270502
271050
  }
270503
271051
  function callRecordFromAbsCall(r, impMap) {
270504
271052
  const argAbs = r.args.map(safeAbsOrUnknown);
270505
- const threw = !!r.threw;
271053
+ const threw = r.threw === true;
271054
+ const threwUnknown = r.threw === void 0;
270506
271055
  const thrownOrResult = safeAbsOrUnknown(r.result);
270507
271056
  const rec = {
270508
271057
  fnName: r.fnName,
270509
271058
  argAbs,
270510
271059
  resultAbs: threw ? neverAbs : thrownOrResult,
270511
- throwsAbs: threw ? thrownOrResult : neverAbs,
271060
+ throwsAbs: threw ? thrownOrResult : threwUnknown ? unknown : neverAbs,
270512
271061
  callLoc: r.callLoc
270513
271062
  };
270514
271063
  const imp = impMap?.get(r.fnName) ?? // `ns.helper` / `Class.method`:先整名,再按首段点号拆命名空间成员
@@ -270978,19 +271527,23 @@ function collectDeclared(file) {
270978
271527
  if (id.type === "Identifier") names2.add(id.name);
270979
271528
  if (id.type === "ObjectPattern") {
270980
271529
  for (const p of id.properties) {
270981
- addId(p.value);
271530
+ addId(p.type === "RestElement" ? p.argument : p.value);
270982
271531
  }
270983
271532
  }
270984
271533
  if (id.type === "ArrayPattern") {
270985
271534
  for (const el of id.elements) addId(el);
270986
271535
  }
271536
+ if (id.type === "AssignmentPattern") addId(id.left);
271537
+ if (id.type === "RestElement") addId(id.argument);
270987
271538
  };
270988
271539
  const visit2 = (node) => {
270989
271540
  if (!node || typeof node !== "object") return;
270990
271541
  const o = node;
270991
271542
  switch (o.type) {
270992
271543
  case "FunctionDeclaration":
271544
+ case "FunctionExpression":
270993
271545
  case "ClassDeclaration":
271546
+ case "ClassExpression":
270994
271547
  addId(o.id);
270995
271548
  break;
270996
271549
  case "VariableDeclarator":
@@ -271035,7 +271588,7 @@ function isStaticStringExpr(node) {
271035
271588
  function isFreeUnknown(name, declared, seen, known) {
271036
271589
  return !declared.has(name) && !known.has(name) && !name.startsWith("$") && !seen.has(name);
271037
271590
  }
271038
- function walkBuiltinUnknown(node, declared, out, seen, known, parentKey) {
271591
+ function walkBuiltinUnknown(node, declared, out, seen, known, namePosition = false) {
271039
271592
  const n = node;
271040
271593
  const flag = (idNode, name) => {
271041
271594
  if (!isFreeUnknown(name, declared, seen, known)) return;
@@ -271057,20 +271610,21 @@ function walkBuiltinUnknown(node, declared, out, seen, known, parentKey) {
271057
271610
  } else if (callee?.type === "Identifier") {
271058
271611
  flag(n.callee, callee.name);
271059
271612
  }
271060
- } else if (n.type === "Identifier" && typeof n.name === "string" && n.name !== "require" && parentKey !== "key" && parentKey !== "property" && parentKey !== "local" && parentKey !== "id") {
271613
+ } else if (n.type === "Identifier" && typeof n.name === "string" && n.name !== "require" && !namePosition) {
271061
271614
  flag(node, n.name);
271062
271615
  }
271063
271616
  for (const key of Object.keys(n)) {
271064
271617
  if (key === "loc" || key === "start" || key === "end") continue;
271618
+ const childIsName = (key === "key" || key === "property") && n.computed !== true || key === "local" || key === "id";
271065
271619
  const v2 = n[key];
271066
271620
  if (Array.isArray(v2)) {
271067
271621
  v2.forEach((x) => {
271068
271622
  if (x && typeof x === "object" && "type" in x) {
271069
- walkBuiltinUnknown(x, declared, out, seen, known, key);
271623
+ walkBuiltinUnknown(x, declared, out, seen, known, childIsName);
271070
271624
  }
271071
271625
  });
271072
271626
  } else if (v2 && typeof v2 === "object" && "type" in v2) {
271073
- walkBuiltinUnknown(v2, declared, out, seen, known, key);
271627
+ walkBuiltinUnknown(v2, declared, out, seen, known, childIsName);
271074
271628
  }
271075
271629
  }
271076
271630
  }
@@ -272834,10 +273388,16 @@ var init_disk_cache = __esm({
272834
273388
  });
272835
273389
 
272836
273390
  // ../service/src/watch-paths.ts
273391
+ function matchFormalSidecarSuffix(path) {
273392
+ for (const suffix of FORMAL_SIDECAR_SUFFIXES) {
273393
+ if (path.slice(-suffix.length).toLowerCase() === suffix) return suffix;
273394
+ }
273395
+ return void 0;
273396
+ }
272837
273397
  function isSidecarPath(path) {
272838
273398
  const lower = path.toLowerCase().replace(/\\/g, "/");
272839
273399
  if (lower.includes(".nudo.draft.")) return false;
272840
- return lower.endsWith(".nudo.js") || lower.endsWith(".nudo.mjs") || lower.endsWith(".nudo.ts");
273400
+ return matchFormalSidecarSuffix(lower) !== void 0;
272841
273401
  }
272842
273402
  function isProjectConfigPath(path) {
272843
273403
  const norm = path.replace(/\\/g, "/");
@@ -272850,13 +273410,13 @@ function isWatchRelevantPath(path) {
272850
273410
  }
272851
273411
  function ambientSourcesOfSidecar(sidecarPath) {
272852
273412
  const norm = sidecarPath.replace(/\\/g, "/");
272853
- const m = norm.match(/^(.*)\.nudo\.(js|ts)$/i);
272854
- if (!m || norm.toLowerCase().includes(".nudo.draft.")) return [];
272855
- const base = m[1];
272856
- const ext = m[2].toLowerCase();
272857
- return ext === "js" ? [`${base}.js`, `${base}.mjs`] : [`${base}.ts`, `${base}.mts`];
273413
+ if (norm.toLowerCase().includes(".nudo.draft.")) return [];
273414
+ const suffix = matchFormalSidecarSuffix(norm);
273415
+ if (!suffix) return [];
273416
+ const base = norm.slice(0, norm.length - suffix.length);
273417
+ return SIDECAR_AMBIENT_SUFFIXES[suffix].map((ext) => `${base}${ext}`);
272858
273418
  }
272859
- var PROJECT_CONFIG_BASENAMES;
273419
+ var PROJECT_CONFIG_BASENAMES, FORMAL_SIDECAR_SUFFIXES, SIDECAR_AMBIENT_SUFFIXES;
272860
273420
  var init_watch_paths = __esm({
272861
273421
  "../service/src/watch-paths.ts"() {
272862
273422
  "use strict";
@@ -272871,6 +273431,12 @@ var init_watch_paths = __esm({
272871
273431
  ".nudorc",
272872
273432
  ".nudorc.json"
272873
273433
  ]);
273434
+ FORMAL_SIDECAR_SUFFIXES = [".nudo.js", ".nudo.mjs", ".nudo.ts"];
273435
+ SIDECAR_AMBIENT_SUFFIXES = {
273436
+ ".nudo.js": [".js", ".mjs"],
273437
+ ".nudo.mjs": [".js", ".mjs"],
273438
+ ".nudo.ts": [".ts", ".mts"]
273439
+ };
272874
273440
  }
272875
273441
  });
272876
273442
 
@@ -273343,7 +273909,7 @@ var init_chunk_4TAFH6ZZ = __esm({
273343
273909
  }
273344
273910
  });
273345
273911
 
273346
- // ../service/dist/chunk-RHEXBPQ6.js
273912
+ // ../service/dist/chunk-2RP2Q6LJ.js
273347
273913
  import { readFileSync as readFileSync17, existsSync as existsSync16, statSync as statSync10, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
273348
273914
  import { resolve as resolvePath2, join as joinPath2 } from "path";
273349
273915
  import { pathToFileURL as pathToFileURL2 } from "url";
@@ -273524,20 +274090,20 @@ function envLimits2(env2) {
273524
274090
  };
273525
274091
  }
273526
274092
  function pick3(key, env2, fallback) {
273527
- const e = env2[key];
273528
- if (typeof e === "number" && Number.isFinite(e)) return e;
273529
274093
  const x = explicit2[key];
273530
274094
  if (typeof x === "number" && Number.isFinite(x)) return clampEntries2(x, fallback);
274095
+ const e = env2[key];
274096
+ if (typeof e === "number" && Number.isFinite(e)) return e;
273531
274097
  const p = fromProject2[key];
273532
274098
  if (typeof p === "number" && Number.isFinite(p)) return clampEntries2(p, fallback);
273533
274099
  return fallback;
273534
274100
  }
273535
274101
  function getSessionCacheLimits2(env2 = process.env) {
273536
- if (envCache2 === null) envCache2 = envLimits2(env2);
274102
+ const fromEnv = envLimits2(env2);
273537
274103
  return {
273538
- maxFiles: pick3("maxFiles", envCache2, DEFAULT_SESSION_CACHE_LIMITS2.maxFiles),
273539
- maxFns: pick3("maxFns", envCache2, DEFAULT_SESSION_CACHE_LIMITS2.maxFns),
273540
- maxEvalRuns: pick3("maxEvalRuns", envCache2, DEFAULT_SESSION_CACHE_LIMITS2.maxEvalRuns)
274104
+ maxFiles: pick3("maxFiles", fromEnv, DEFAULT_SESSION_CACHE_LIMITS2.maxFiles),
274105
+ maxFns: pick3("maxFns", fromEnv, DEFAULT_SESSION_CACHE_LIMITS2.maxFns),
274106
+ maxEvalRuns: pick3("maxEvalRuns", fromEnv, DEFAULT_SESSION_CACHE_LIMITS2.maxEvalRuns)
273541
274107
  };
273542
274108
  }
273543
274109
  function setSessionCacheFromProject2(partial2) {
@@ -273646,15 +274212,22 @@ function findProjectConfig2(startDir) {
273646
274212
  while (dir !== root) {
273647
274213
  const pkgPath = resolve10(dir, "package.json");
273648
274214
  if (existsSync22(pkgPath)) {
274215
+ let pkg;
273649
274216
  try {
273650
- const pkg = JSON.parse(readFileSync22(pkgPath, "utf-8"));
273651
- if (pkg.nudo) {
273652
- const nudo = pkg.nudo;
273653
- setSessionCacheFromProject2(nudo.sessionCache);
273654
- applyBForkBudgetFromConfig2(nudo);
273655
- return { config: nudo, projectDir: dir };
273656
- }
273657
- } catch {
274217
+ pkg = JSON.parse(readFileSync22(pkgPath, "utf-8"));
274218
+ } catch (err) {
274219
+ process.stderr?.write?.(
274220
+ `nudo: package.json parse failed at ${pkgPath} (${err instanceof Error ? err.message : String(err)}); project config disabled for this subtree (defaults + env apply)
274221
+ `
274222
+ );
274223
+ applyBForkBudgetFromConfig2(null);
274224
+ return null;
274225
+ }
274226
+ const nudo = pkg && typeof pkg === "object" ? pkg.nudo : void 0;
274227
+ if (nudo) {
274228
+ setSessionCacheFromProject2(nudo.sessionCache);
274229
+ applyBForkBudgetFromConfig2(nudo);
274230
+ return { config: nudo, projectDir: dir };
273658
274231
  }
273659
274232
  }
273660
274233
  const parent = dirname18(dir);
@@ -273806,9 +274379,9 @@ function resolveNpmJsEntryDetailed2(source, fromDir) {
273806
274379
  }
273807
274380
  return { path: tryInsidePkg2(pkgDir, subpath, tryFile), exportsUnresolved: false };
273808
274381
  }
273809
- var BUILTIN_ERROR_CLASSES2, BUILTIN_PROTOTYPE_CLASSES2, numA2, strA2, boolA2, unkA2, undefA2, nullA2, OBJECT_PROTOTYPE_METHODS2, BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS2, BoundedLruMap4, envFactories2, impliedDeps2, PATH_ENV_CACHE_MAX2, PATH_ENV_BY_PATH_MAX2, PATH_ENV_BASE_DIRS_MAX2, pathEnvCache2, pathEnvByPath2, pathEnvBaseDirs2, DEFAULT_SESSION_CACHE_LIMITS2, HARD_CAP2, explicit2, fromProject2, envCache2, DEFAULT_ANALYSIS_EXCLUDE2, DEFAULT_ANALYSIS_MODE2, JS_CONDITIONS2;
273810
- var init_chunk_RHEXBPQ6 = __esm({
273811
- "../service/dist/chunk-RHEXBPQ6.js"() {
274382
+ var BUILTIN_ERROR_CLASSES2, BUILTIN_PROTOTYPE_CLASSES2, numA2, strA2, boolA2, unkA2, undefA2, nullA2, OBJECT_PROTOTYPE_METHODS2, BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS2, BoundedLruMap4, envFactories2, impliedDeps2, PATH_ENV_CACHE_MAX2, PATH_ENV_BY_PATH_MAX2, PATH_ENV_BASE_DIRS_MAX2, pathEnvCache2, pathEnvByPath2, pathEnvBaseDirs2, DEFAULT_SESSION_CACHE_LIMITS2, HARD_CAP2, explicit2, fromProject2, DEFAULT_ANALYSIS_EXCLUDE2, DEFAULT_ANALYSIS_MODE2, JS_CONDITIONS2;
274383
+ var init_chunk_2RP2Q6LJ = __esm({
274384
+ "../service/dist/chunk-2RP2Q6LJ.js"() {
273812
274385
  "use strict";
273813
274386
  init_src();
273814
274387
  init_es();
@@ -274074,7 +274647,6 @@ var init_chunk_RHEXBPQ6 = __esm({
274074
274647
  HARD_CAP2 = 65536;
274075
274648
  explicit2 = {};
274076
274649
  fromProject2 = {};
274077
- envCache2 = null;
274078
274650
  DEFAULT_ANALYSIS_EXCLUDE2 = [
274079
274651
  "**/node_modules/**",
274080
274652
  "**/dist/**",
@@ -274085,7 +274657,7 @@ var init_chunk_RHEXBPQ6 = __esm({
274085
274657
  }
274086
274658
  });
274087
274659
 
274088
- // ../service/dist/chunk-XY75TNBY.js
274660
+ // ../service/dist/chunk-6GFPZGQS.js
274089
274661
  import { existsSync as existsSync17, statSync as statSync11, readFileSync as readFileSync18 } from "fs";
274090
274662
  import { dirname as dirname19, resolve as resolve11, join as join13 } from "path";
274091
274663
  import { readFileSync as readFileSync42, statSync as statSync32 } from "fs";
@@ -274696,7 +275268,7 @@ function collectRequireSpecs2(node, out) {
274696
275268
  };
274697
275269
  visit2(node);
274698
275270
  }
274699
- function buildModulesForFile2(source, fromFile, load, evalDep, depth, maxDepth, onIssue) {
275271
+ function buildModulesForFile2(source, fromFile, load, evalDep, depth, maxDepth, onIssue, localDepsOut) {
274700
275272
  const modules = {};
274701
275273
  for (const spec of importSpecs2(source)) {
274702
275274
  if (spec.startsWith(".") || spec.startsWith("/")) {
@@ -274706,6 +275278,7 @@ function buildModulesForFile2(source, fromFile, load, evalDep, depth, maxDepth,
274706
275278
  continue;
274707
275279
  }
274708
275280
  modules[spec] = evalDep(childPath, spec, fromFile, depth + 1);
275281
+ localDepsOut?.push(stablePathKey(childPath));
274709
275282
  } else if (!spec.startsWith("node:")) {
274710
275283
  const resolved = resolveNpmJsEntryDetailed2(spec, dirname32(fromFile));
274711
275284
  if (resolved.exportsUnresolved) {
@@ -274717,6 +275290,7 @@ function buildModulesForFile2(source, fromFile, load, evalDep, depth, maxDepth,
274717
275290
  }
274718
275291
  if (resolved.path) {
274719
275292
  const executed = evalDep(resolved.path, spec, fromFile, depth + 1);
275293
+ localDepsOut?.push(stablePathKey(resolved.path));
274720
275294
  if (executed && (executed.default !== void 0 || Object.keys(executed.named ?? {}).length > 0)) {
274721
275295
  modules[spec] = executed;
274722
275296
  continue;
@@ -274729,6 +275303,18 @@ function buildModulesForFile2(source, fromFile, load, evalDep, depth, maxDepth,
274729
275303
  void maxDepth;
274730
275304
  return modules;
274731
275305
  }
275306
+ function depFingerprintCurrent2(deps) {
275307
+ for (const d of deps) {
275308
+ let src;
275309
+ try {
275310
+ src = readFileSync42(d.path, "utf-8");
275311
+ } catch {
275312
+ return false;
275313
+ }
275314
+ if (hashSource(src) !== d.hash) return false;
275315
+ }
275316
+ return true;
275317
+ }
274732
275318
  function moduleLabel2(p) {
274733
275319
  const parts = p.split(/[/\\]/);
274734
275320
  return parts[parts.length - 1] || p;
@@ -274813,7 +275399,7 @@ function collectMissingExportIssues2(source, modules, fromFile) {
274813
275399
  const checkName = (spec, name, kind) => {
274814
275400
  const mod2 = modules[spec];
274815
275401
  if (!mod2?.evaluated) return;
274816
- const has = name === "default" ? mod2.default !== void 0 : name in mod2.named;
275402
+ const has = name === "default" ? mod2.default !== void 0 : Object.hasOwn(mod2.named, name);
274817
275403
  if (!has) push(spec, name, kind);
274818
275404
  };
274819
275405
  const body = file.program?.body ?? [];
@@ -274846,11 +275432,15 @@ function evalAbsModuleGraph2(entrySource, entryFile, opts = {}) {
274846
275432
  const loading = [];
274847
275433
  const issues = [];
274848
275434
  const seenIssue = /* @__PURE__ */ new Set();
275435
+ const issueFlow = [];
275436
+ const depMeta = /* @__PURE__ */ new Map();
274849
275437
  const pushIssue2 = (kind, label, reason) => {
275438
+ const iss = { kind, label, reason };
275439
+ issueFlow.push(iss);
274850
275440
  const key = `${kind}:${label}`;
274851
275441
  if (seenIssue.has(key)) return;
274852
275442
  seenIssue.add(key);
274853
- issues.push({ kind, label, reason });
275443
+ issues.push(iss);
274854
275444
  };
274855
275445
  function evalDep(absPathRaw, spec, fromFile, depth) {
274856
275446
  const absPath = stablePathKey(absPathRaw);
@@ -274868,9 +275458,13 @@ function evalAbsModuleGraph2(entrySource, entryFile, opts = {}) {
274868
275458
  if (shared) {
274869
275459
  try {
274870
275460
  const st = statSync32(absPath);
274871
- if (st.mtimeMs === shared.mtimeMs && st.size === shared.size) {
275461
+ if (st.mtimeMs === shared.mtimeMs && st.size === shared.size && depFingerprintCurrent2(shared.depFingerprints)) {
274872
275462
  for (const iss of shared.issues) pushIssue2(iss.kind, iss.label, iss.reason);
274873
275463
  cache2.set(absPath, shared.exports);
275464
+ depMeta.set(absPath, {
275465
+ contentHash: shared.contentHash,
275466
+ depFingerprints: shared.depFingerprints
275467
+ });
274874
275468
  return shared.exports;
274875
275469
  }
274876
275470
  } catch {
@@ -274908,7 +275502,8 @@ function evalAbsModuleGraph2(entrySource, entryFile, opts = {}) {
274908
275502
  loading.pop();
274909
275503
  return placeholder;
274910
275504
  }
274911
- const issueStart = issues.length;
275505
+ const issueStart = issueFlow.length;
275506
+ const localDeps = [];
274912
275507
  const modules2 = buildModulesForFile2(
274913
275508
  source,
274914
275509
  absPath,
@@ -274918,7 +275513,8 @@ function evalAbsModuleGraph2(entrySource, entryFile, opts = {}) {
274918
275513
  maxDepth,
274919
275514
  (kind, label, reason) => {
274920
275515
  pushIssue2(kind, label, reason);
274921
- }
275516
+ },
275517
+ localDeps
274922
275518
  );
274923
275519
  let exports;
274924
275520
  const evalRun = tryRunTranspiled(source, { mode: "analyze", modules: modules2 });
@@ -274935,17 +275531,40 @@ function evalAbsModuleGraph2(entrySource, entryFile, opts = {}) {
274935
275531
  if (exports.default !== void 0) placeholder.default = exports.default;
274936
275532
  if (exports.evaluated) placeholder.evaluated = true;
274937
275533
  cache2.set(absPath, placeholder);
275534
+ const depFingerprints = [];
275535
+ const seenDep = /* @__PURE__ */ new Set();
275536
+ let untrackable = false;
275537
+ for (const depPath of localDeps) {
275538
+ const meta = depMeta.get(depPath);
275539
+ if (!meta) {
275540
+ untrackable = true;
275541
+ break;
275542
+ }
275543
+ if (!seenDep.has(depPath)) {
275544
+ seenDep.add(depPath);
275545
+ depFingerprints.push({ path: depPath, hash: meta.contentHash });
275546
+ }
275547
+ for (const e of meta.depFingerprints) {
275548
+ if (seenDep.has(e.path)) continue;
275549
+ seenDep.add(e.path);
275550
+ depFingerprints.push(e);
275551
+ }
275552
+ }
275553
+ const subtree = untrackable ? null : { contentHash: hashSource(source), depFingerprints };
275554
+ depMeta.set(absPath, subtree);
274938
275555
  let fingerprint;
274939
275556
  try {
274940
275557
  const st = statSync32(absPath);
274941
275558
  fingerprint = { mtimeMs: st.mtimeMs, size: st.size };
274942
275559
  } catch {
274943
275560
  }
274944
- if (fingerprint) {
275561
+ if (fingerprint && subtree) {
274945
275562
  absModuleCache2.set(absPath, {
274946
275563
  ...fingerprint,
275564
+ contentHash: subtree.contentHash,
275565
+ depFingerprints: subtree.depFingerprints,
274947
275566
  exports: placeholder,
274948
- issues: issues.slice(issueStart)
275567
+ issues: issueFlow.slice(issueStart)
274949
275568
  });
274950
275569
  }
274951
275570
  return placeholder;
@@ -274989,7 +275608,7 @@ function importLocalBindings2(source, modules) {
274989
275608
  } else if (sp.type === "ImportSpecifier") {
274990
275609
  const imported = sp.imported?.type === "StringLiteral" ? sp.imported.value : sp.imported?.name;
274991
275610
  if (imported === void 0) continue;
274992
- const absVal = imported === "default" ? mod2.default : mod2.named[imported];
275611
+ const absVal = imported === "default" ? mod2.default : Object.hasOwn(mod2.named, imported) ? mod2.named[imported] : void 0;
274993
275612
  out.set(local, absVal ?? unknown);
274994
275613
  }
274995
275614
  }
@@ -275132,7 +275751,7 @@ function pickNamed2(mock2, names2) {
275132
275751
  if (mock2.default) def = mock2.default;
275133
275752
  continue;
275134
275753
  }
275135
- const v2 = mock2.named[n];
275754
+ const v2 = Object.hasOwn(mock2.named, n) ? mock2.named[n] : void 0;
275136
275755
  if (v2 !== void 0) named[n] = v2;
275137
275756
  }
275138
275757
  return def !== void 0 ? { named, default: def } : { named };
@@ -275192,6 +275811,7 @@ function applyMockModuleDirectivesFromSource2(source, base, opts) {
275192
275811
  return applyMockModuleDirectives2(base, fileDirectives, opts);
275193
275812
  }
275194
275813
  function analysisCacheGet2(filePath, source, auxKey) {
275814
+ if (getSessionCacheLimits2().maxFiles <= 0) return void 0;
275195
275815
  const key = stablePathKey(filePath);
275196
275816
  const e = analysisByFile2.get(key);
275197
275817
  if (!e) return void 0;
@@ -275209,6 +275829,7 @@ function analysisCacheSet2(filePath, source, auxKey, value) {
275209
275829
  if (oldest === void 0) break;
275210
275830
  analysisByFile2.delete(oldest);
275211
275831
  }
275832
+ analysisByFile2.delete(key);
275212
275833
  analysisByFile2.set(key, { source, auxKey, value });
275213
275834
  }
275214
275835
  function normalizeFnCacheKey2(key) {
@@ -275218,6 +275839,7 @@ function normalizeFnCacheKey2(key) {
275218
275839
  return stablePathKey(filePath) + rest;
275219
275840
  }
275220
275841
  function fnAnalysisCacheGet2(key) {
275842
+ if (getSessionCacheLimits2().maxFns <= 0) return void 0;
275221
275843
  const k = normalizeFnCacheKey2(key);
275222
275844
  const hit = fnAnalysisCache2.get(k);
275223
275845
  if (hit !== void 0) {
@@ -275235,6 +275857,7 @@ function fnAnalysisCacheSet2(key, value) {
275235
275857
  if (oldest === void 0) break;
275236
275858
  fnAnalysisCache2.delete(oldest);
275237
275859
  }
275860
+ fnAnalysisCache2.delete(k);
275238
275861
  fnAnalysisCache2.set(k, value);
275239
275862
  }
275240
275863
  function caseDirectiveKey2(directives, formatAbs6) {
@@ -275399,7 +276022,11 @@ function collectEvalReplacements2(source) {
275399
276022
  }
275400
276023
  };
275401
276024
  visitStmts(file.program.body);
275402
- } catch {
276025
+ } catch (err) {
276026
+ if (targets.length > 0 || asTargets.length > 0) {
276027
+ throw err;
276028
+ }
276029
+ noteEvalFallback(err);
275403
276030
  }
275404
276031
  return { targets, values, asTargets, asValues };
275405
276032
  }
@@ -275426,6 +276053,7 @@ function evalCacheSet2(filePath, stableSource, mode, envKey, mockKey, depKey, va
275426
276053
  if (oldest === void 0) break;
275427
276054
  evalRunByFile2.delete(oldest);
275428
276055
  }
276056
+ evalRunByFile2.delete(key);
275429
276057
  evalRunByFile2.set(key, { stableSource, mode, envKey, mockKey, depKey, value });
275430
276058
  }
275431
276059
  function tryRunEval2(source, filePath, opts = {}) {
@@ -275436,7 +276064,7 @@ function tryRunEval2(source, filePath, opts = {}) {
275436
276064
  const stable = stableAnalyzeKeySource(source);
275437
276065
  const depKey = evalDepKey2(source, filePath);
275438
276066
  const canCache = depKey !== null;
275439
- if (canCache) {
276067
+ if (canCache && getSessionCacheLimits2().maxEvalRuns > 0) {
275440
276068
  const cacheKey = stablePathKey(filePath);
275441
276069
  const cached2 = evalRunByFile2.get(cacheKey);
275442
276070
  if (cached2 && cached2.stableSource === stable && cached2.mode === mode && cached2.envKey === envKey && cached2.mockKey === mockKey && cached2.depKey === depKey) {
@@ -275500,7 +276128,7 @@ function tryRunEval2(source, filePath, opts = {}) {
275500
276128
  function tryEvalCallFull2(source, filePath, fnName, args, opts = {}) {
275501
276129
  const run = tryRunEval2(source, filePath, { envNames: opts.envNames, mocks: opts.mocks });
275502
276130
  if (!run) return void 0;
275503
- if (!(fnName in run.exports)) return void 0;
276131
+ if (!Object.hasOwn(run.exports, fnName)) return void 0;
275504
276132
  const collected = [];
275505
276133
  const memberDiags = [];
275506
276134
  const prevCall = opts.collectCalls ? setEvalCallCollector((r) => collected.push(r)) : void 0;
@@ -275525,7 +276153,14 @@ function tryEvalCall2(source, filePath, fnName, args, opts = {}) {
275525
276153
  const full = tryEvalCallFull2(source, filePath, fnName, args, opts);
275526
276154
  if (!full) return void 0;
275527
276155
  const r = full.result;
275528
- if (!r) return void 0;
276156
+ if (!isAbsVal(r)) {
276157
+ noteEvalFallback(
276158
+ new Error(
276159
+ `tryEvalCall: '${fnName}' result is not an Abs value (producer contract violation)`
276160
+ )
276161
+ );
276162
+ return void 0;
276163
+ }
275529
276164
  if (r.shape.k === "never" && full.throws.shape.k !== "never") {
275530
276165
  return void 0;
275531
276166
  }
@@ -275686,7 +276321,7 @@ function absFromSinon2(sinonExpr) {
275686
276321
  }
275687
276322
  return constantMockFn2(unknown);
275688
276323
  }
275689
- function isAbsVal22(v2) {
276324
+ function isAbsVal32(v2) {
275690
276325
  return !!v2 && typeof v2 === "object" && "shape" in v2 && "conf" in v2;
275691
276326
  }
275692
276327
  function loadFromMockBinding2(name, fromPath, fromFile, loadModule) {
@@ -275718,7 +276353,7 @@ function loadFromMockBinding2(name, fromPath, fromFile, loadModule) {
275718
276353
  let absVal;
275719
276354
  if (val === void 0) {
275720
276355
  absVal = undefAbs();
275721
- } else if (isAbsVal22(val)) {
276356
+ } else if (isAbsVal32(val)) {
275722
276357
  absVal = val;
275723
276358
  } else if (typeof val === "function") {
275724
276359
  const fn2 = val;
@@ -275979,13 +276614,14 @@ function safeAbsOrUnknown2(a) {
275979
276614
  }
275980
276615
  function callRecordFromAbsCall2(r, impMap) {
275981
276616
  const argAbs = r.args.map(safeAbsOrUnknown2);
275982
- const threw = !!r.threw;
276617
+ const threw = r.threw === true;
276618
+ const threwUnknown = r.threw === void 0;
275983
276619
  const thrownOrResult = safeAbsOrUnknown2(r.result);
275984
276620
  const rec = {
275985
276621
  fnName: r.fnName,
275986
276622
  argAbs,
275987
276623
  resultAbs: threw ? neverAbs2 : thrownOrResult,
275988
- throwsAbs: threw ? thrownOrResult : neverAbs2,
276624
+ throwsAbs: threw ? thrownOrResult : threwUnknown ? unknown : neverAbs2,
275989
276625
  callLoc: r.callLoc
275990
276626
  };
275991
276627
  const imp = impMap?.get(r.fnName) ?? // `ns.helper` / `Class.method`:先整名,再按首段点号拆命名空间成员
@@ -276089,19 +276725,23 @@ function collectDeclared2(file) {
276089
276725
  if (id.type === "Identifier") names2.add(id.name);
276090
276726
  if (id.type === "ObjectPattern") {
276091
276727
  for (const p of id.properties) {
276092
- addId(p.value);
276728
+ addId(p.type === "RestElement" ? p.argument : p.value);
276093
276729
  }
276094
276730
  }
276095
276731
  if (id.type === "ArrayPattern") {
276096
276732
  for (const el of id.elements) addId(el);
276097
276733
  }
276734
+ if (id.type === "AssignmentPattern") addId(id.left);
276735
+ if (id.type === "RestElement") addId(id.argument);
276098
276736
  };
276099
276737
  const visit2 = (node) => {
276100
276738
  if (!node || typeof node !== "object") return;
276101
276739
  const o = node;
276102
276740
  switch (o.type) {
276103
276741
  case "FunctionDeclaration":
276742
+ case "FunctionExpression":
276104
276743
  case "ClassDeclaration":
276744
+ case "ClassExpression":
276105
276745
  addId(o.id);
276106
276746
  break;
276107
276747
  case "VariableDeclarator":
@@ -276146,7 +276786,7 @@ function isStaticStringExpr2(node) {
276146
276786
  function isFreeUnknown2(name, declared, seen, known) {
276147
276787
  return !declared.has(name) && !known.has(name) && !name.startsWith("$") && !seen.has(name);
276148
276788
  }
276149
- function walkBuiltinUnknown2(node, declared, out, seen, known, parentKey) {
276789
+ function walkBuiltinUnknown2(node, declared, out, seen, known, namePosition = false) {
276150
276790
  const n = node;
276151
276791
  const flag = (idNode, name) => {
276152
276792
  if (!isFreeUnknown2(name, declared, seen, known)) return;
@@ -276168,20 +276808,21 @@ function walkBuiltinUnknown2(node, declared, out, seen, known, parentKey) {
276168
276808
  } else if (callee?.type === "Identifier") {
276169
276809
  flag(n.callee, callee.name);
276170
276810
  }
276171
- } else if (n.type === "Identifier" && typeof n.name === "string" && n.name !== "require" && parentKey !== "key" && parentKey !== "property" && parentKey !== "local" && parentKey !== "id") {
276811
+ } else if (n.type === "Identifier" && typeof n.name === "string" && n.name !== "require" && !namePosition) {
276172
276812
  flag(node, n.name);
276173
276813
  }
276174
276814
  for (const key of Object.keys(n)) {
276175
276815
  if (key === "loc" || key === "start" || key === "end") continue;
276816
+ const childIsName = (key === "key" || key === "property") && n.computed !== true || key === "local" || key === "id";
276176
276817
  const v2 = n[key];
276177
276818
  if (Array.isArray(v2)) {
276178
276819
  v2.forEach((x) => {
276179
276820
  if (x && typeof x === "object" && "type" in x) {
276180
- walkBuiltinUnknown2(x, declared, out, seen, known, key);
276821
+ walkBuiltinUnknown2(x, declared, out, seen, known, childIsName);
276181
276822
  }
276182
276823
  });
276183
276824
  } else if (v2 && typeof v2 === "object" && "type" in v2) {
276184
- walkBuiltinUnknown2(v2, declared, out, seen, known, key);
276825
+ walkBuiltinUnknown2(v2, declared, out, seen, known, childIsName);
276185
276826
  }
276186
276827
  }
276187
276828
  }
@@ -277379,10 +278020,10 @@ function analyzeFile2(filePath, source, activeCases, externalCallRecords, loadMo
277379
278020
  return cloneAnalysisResult2(result);
277380
278021
  }
277381
278022
  var MODULE_RESOLVE_EXTS2, ModuleReadError2, neverAbs2, undefAbs4, MAX_RECORD_TYPE_NODES2, ABS_MODULE_CACHE_MAX2, absModuleCache2, ENV_DEPENDENTS_MAX2, ENV_DEPENDENTS_PER_KEY_MAX2, envDependents2, analysisByFile2, fnAnalysisCache2, envHarvestConflictCollector2, evalRunByFile2, KNOWN_GLOBALS2, DEFAULT_CALLSITE_BUDGET3, COLLAPSE_LITERAL_THRESHOLD2;
277382
- var init_chunk_XY75TNBY = __esm({
277383
- "../service/dist/chunk-XY75TNBY.js"() {
278023
+ var init_chunk_6GFPZGQS = __esm({
278024
+ "../service/dist/chunk-6GFPZGQS.js"() {
277384
278025
  "use strict";
277385
- init_chunk_RHEXBPQ6();
278026
+ init_chunk_2RP2Q6LJ();
277386
278027
  init_src2();
277387
278028
  init_src();
277388
278029
  init_src2();
@@ -277518,12 +278159,14 @@ __export(emit_exports, {
277518
278159
  insertGeneratedCaseDirectives: () => insertGeneratedCaseDirectives,
277519
278160
  interfaceSurface: () => interfaceSurface,
277520
278161
  isDraftableEntry: () => isDraftableEntry,
278162
+ matchSidecarFnDecl: () => matchSidecarFnDecl,
277521
278163
  materializeAction: () => materializeAction,
277522
278164
  projectAbsToSchema: () => projectAbsToSchema,
277523
278165
  schemaNodeToZod: () => schemaNodeToZod,
277524
278166
  serializeCaseArg: () => serializeCaseArg,
277525
278167
  serializeCaseJson: () => serializeCaseJson,
277526
278168
  shapeDslFromFields: () => shapeDslFromFields,
278169
+ sidecarBindingFor: () => sidecarBindingFor,
277527
278170
  sidecarDraftPath: () => sidecarDraftPath,
277528
278171
  stripGeneratedCaseDirectives: () => stripGeneratedCaseDirectives,
277529
278172
  titleKindFor: () => titleKindFor,
@@ -277711,6 +278354,18 @@ function serializeObjectKey(key) {
277711
278354
  return `"${key}"`;
277712
278355
  }
277713
278356
  function serializeCaseArg(a) {
278357
+ return serializeCaseArgB(a, new ProjectionBudget());
278358
+ }
278359
+ function serializeCaseArgB(a, budget) {
278360
+ const stop2 = budget.enter(a);
278361
+ if (stop2) return null;
278362
+ try {
278363
+ return serializeCaseArgInner(a, budget);
278364
+ } finally {
278365
+ budget.exit();
278366
+ }
278367
+ }
278368
+ function serializeCaseArgInner(a, budget) {
277714
278369
  const s = a.shape;
277715
278370
  if (a.term?.op === "lit") {
277716
278371
  const v2 = a.term.value;
@@ -277740,20 +278395,21 @@ function serializeCaseArg(a) {
277740
278395
  case "sum": {
277741
278396
  const parts = [];
277742
278397
  for (const member of s.members) {
277743
- const ser = serializeCaseArg(member);
278398
+ const ser = serializeCaseArgB(member, budget);
277744
278399
  if (ser === null) return null;
277745
278400
  parts.push(ser);
277746
278401
  }
277747
278402
  return `union(${parts.join(", ")})`;
277748
278403
  }
277749
278404
  case "arr": {
277750
- const el = serializeCaseArg(s.element);
278405
+ const el = serializeCaseArgB(s.element, budget);
277751
278406
  return el === null ? null : `array(${el})`;
277752
278407
  }
277753
278408
  case "tuple": {
278409
+ if (s.rest) return null;
277754
278410
  const parts = [];
277755
278411
  for (const el of s.elements) {
277756
- const ser = serializeCaseArg(el);
278412
+ const ser = serializeCaseArgB(el, budget);
277757
278413
  if (ser === null) return null;
277758
278414
  parts.push(ser);
277759
278415
  }
@@ -277762,7 +278418,7 @@ function serializeCaseArg(a) {
277762
278418
  case "obj": {
277763
278419
  const parts = [];
277764
278420
  for (const [key, slot] of Object.entries(s.slots)) {
277765
- const vs = serializeCaseArg(slot.value);
278421
+ const vs = serializeCaseArgB(slot.value, budget);
277766
278422
  if (vs === null) return null;
277767
278423
  const ks = serializeObjectKey(key);
277768
278424
  if (ks === null) return null;
@@ -278039,6 +278695,17 @@ function unifiedDiff(a, b, path) {
278039
278695
  }
278040
278696
  return out.join("\n") + "\n";
278041
278697
  }
278698
+ function exportNameRepr(name) {
278699
+ return JS_EXPORT_NAME.test(name) ? name : JSON.stringify(name);
278700
+ }
278701
+ function directBindingOk(name, taken) {
278702
+ return isJsBindingIdent(name) && !taken(name);
278703
+ }
278704
+ function allocNudoBinding(taken) {
278705
+ let n = 1;
278706
+ while (taken(`_nudo_${n}`)) n++;
278707
+ return `_nudo_${n}`;
278708
+ }
278042
278709
  async function emitInterface(filePath, opts) {
278043
278710
  const abs2 = resolve23(filePath);
278044
278711
  if (isNodeModulesPath(abs2) || isNodeModulesPath(sidecarPathOf(abs2))) {
@@ -278077,7 +278744,10 @@ async function emitInterface(filePath, opts) {
278077
278744
  `sidecar '${relative6(process.cwd(), sidecarPath) || sidecarPath}' is not parseable; fix it before emitting`
278078
278745
  );
278079
278746
  }
278080
- for (const n of generatedNames) declared.delete(n);
278747
+ for (const s of sections) {
278748
+ for (const n of s.names) declared.delete(n);
278749
+ for (const b of s.bindings) declared.delete(b);
278750
+ }
278081
278751
  const analysis = await analyzeFileAsync2(
278082
278752
  abs2,
278083
278753
  source,
@@ -278090,16 +278760,47 @@ async function emitInterface(filePath, opts) {
278090
278760
  for (const f of analysis.functions) {
278091
278761
  if (!fnByName.has(f.name)) fnByName.set(f.name, f);
278092
278762
  }
278093
- const fileExportOrder = analysis.functions.map((f) => f.name).filter((n) => exported.has(n));
278763
+ const identityOfLocal = (local) => exportedNameOfLocal(source, local) ?? local;
278764
+ const localOf = /* @__PURE__ */ new Map();
278765
+ const identityOfFn = /* @__PURE__ */ new Map();
278766
+ for (const f of analysis.functions) {
278767
+ if (identityOfFn.has(f.name)) continue;
278768
+ const identity = identityOfLocal(f.name);
278769
+ identityOfFn.set(f.name, identity);
278770
+ if (!localOf.has(identity)) localOf.set(identity, f.name);
278771
+ }
278772
+ const fileExportOrder = [
278773
+ ...new Set(
278774
+ analysis.functions.map((f) => identityOfFn.get(f.name)).filter((n) => exported.has(n))
278775
+ )
278776
+ ];
278094
278777
  let targetNames;
278095
278778
  if (opts.fnNames && opts.fnNames.length > 0) {
278096
- targetNames = [...new Set(opts.fnNames)];
278779
+ targetNames = [...new Set(opts.fnNames.map((n) => identityOfFn.get(n) ?? n))];
278097
278780
  } else if (opts.all) {
278098
278781
  targetNames = fileExportOrder;
278099
278782
  } else {
278100
278783
  targetNames = fileExportOrder.filter((n) => generatedNames.has(n));
278101
278784
  }
278102
278785
  const targetSet = new Set(targetNames);
278786
+ const takenBindings = /* @__PURE__ */ new Set();
278787
+ const claimFrom = (text) => {
278788
+ for (const n of topLevelDeclaredNames(text) ?? []) takenBindings.add(n);
278789
+ };
278790
+ if (opts.mode === "update") {
278791
+ claimFrom(removeSections(sidecarSrc, sections));
278792
+ for (const s of sections) {
278793
+ if (!s.names.some((n) => targetSet.has(n))) claimFrom(s.text);
278794
+ }
278795
+ } else {
278796
+ claimFrom(sidecarSrc);
278797
+ }
278798
+ const bindingOf = /* @__PURE__ */ new Map();
278799
+ for (const name of targetNames) {
278800
+ const binding = directBindingOk(name, (n) => takenBindings.has(n)) ? name : allocNudoBinding((n) => takenBindings.has(n));
278801
+ takenBindings.add(binding);
278802
+ bindingOf.set(name, binding);
278803
+ }
278103
278804
  const written = [];
278104
278805
  const skipped = [];
278105
278806
  const issues = [];
@@ -278108,9 +278809,9 @@ async function emitInterface(filePath, opts) {
278108
278809
  const planFor = (name) => {
278109
278810
  const hit = planCache.get(name);
278110
278811
  if (hit) return hit;
278111
- const fn2 = fnByName.get(name);
278812
+ const fn2 = fnByName.get(localOf.get(name) ?? name);
278112
278813
  const dsl = fn2 === void 0 ? void 0 : projectFunctionDsl(fn2);
278113
- const roundTrip = dsl !== void 0 && roundTrips(sectionText(name, dsl, srcRel), name);
278814
+ const roundTrip = dsl !== void 0 && roundTrips(sectionText(name, dsl, srcRel, bindingOf.get(name)), name);
278114
278815
  const rec = { dsl, roundTrip };
278115
278816
  planCache.set(name, rec);
278116
278817
  return rec;
@@ -278125,7 +278826,7 @@ async function emitInterface(filePath, opts) {
278125
278826
  }
278126
278827
  const acceptedAtomic = /* @__PURE__ */ new Set();
278127
278828
  for (const name of targetNames) {
278128
- if (!exported.has(name) || !fnByName.has(name)) {
278829
+ if (!exported.has(name) || !fnByName.has(localOf.get(name) ?? name)) {
278129
278830
  skipped.push({
278130
278831
  fn: name,
278131
278832
  reason: "not-an-export"
@@ -278165,7 +278866,7 @@ async function emitInterface(filePath, opts) {
278165
278866
  }
278166
278867
  continue;
278167
278868
  }
278168
- const text = sectionText(name, plan.dsl, srcRel);
278869
+ const text = sectionText(name, plan.dsl, srcRel, bindingOf.get(name));
278169
278870
  if (opts.mode === "add" && prev !== void 0) {
278170
278871
  skipped.push({ fn: name, reason: "no-change" });
278171
278872
  continue;
@@ -278262,10 +278963,12 @@ function projectFunctionDsl(fn2) {
278262
278963
  const ret = retConstraint === void 0 ? "" : `, ${formatConstraint(retConstraint)}`;
278263
278964
  return `fn({ ${paramParts.join(", ")} }${ret})`;
278264
278965
  }
278265
- function sectionText(fn2, dsl, srcRel) {
278966
+ function sectionText(fn2, dsl, srcRel, binding = fn2) {
278967
+ const decl = binding === fn2 ? `export const ${fn2} = ${dsl};` : `const ${binding} = ${dsl};
278968
+ export { ${binding} as ${exportNameRepr(fn2)} };`;
278266
278969
  return `${GENERATED_HEADER}
278267
278970
  // source: ${sanitizeCommentText(`${srcRel}:${fn2}`)}
278268
- export const ${fn2} = ${dsl};
278971
+ ${decl}
278269
278972
  `;
278270
278973
  }
278271
278974
  function roundTrips(text, fn2) {
@@ -278288,23 +278991,62 @@ function collectGeneratedSections(sidecarSrc) {
278288
278991
  } catch {
278289
278992
  throw new Error("sidecar is not parseable");
278290
278993
  }
278994
+ const stmts = ast.program.body;
278291
278995
  const out = [];
278292
- for (const stmt of ast.program.body) {
278996
+ for (let i = 0; i < stmts.length; i++) {
278997
+ const stmt = stmts[i];
278293
278998
  if (stmt.type !== "ExportNamedDeclaration" || stmt.source) continue;
278294
- const d = stmt.declaration;
278295
- if (!d || stmt.start == null || stmt.end == null) continue;
278999
+ if (stmt.end == null) continue;
279000
+ const chain = [];
279001
+ for (let j = i; j >= 0; j--) {
279002
+ const p = stmts[j];
279003
+ if (j !== i && p.type !== "ImportDeclaration" && p.type !== "VariableDeclaration") break;
279004
+ chain.unshift(p);
279005
+ }
279006
+ let blockStart;
279007
+ for (const c of chain) {
279008
+ if (c.start == null) continue;
279009
+ const b = generatedCommentBlockStart(sidecarSrc, c.start);
279010
+ if (b !== void 0) {
279011
+ blockStart = b;
279012
+ break;
279013
+ }
279014
+ }
279015
+ if (blockStart === void 0) continue;
278296
279016
  const names2 = [];
278297
- if (d.type === "VariableDeclaration") {
278298
- for (const decl of d.declarations) {
278299
- if (decl.id.type === "Identifier") names2.push(decl.id.name);
279017
+ const bindings = [];
279018
+ const d = stmt.declaration;
279019
+ if (d) {
279020
+ if (d.type === "VariableDeclaration") {
279021
+ for (const decl of d.declarations) {
279022
+ if (decl.id.type === "Identifier") {
279023
+ names2.push(decl.id.name);
279024
+ bindings.push(decl.id.name);
279025
+ }
279026
+ }
279027
+ } else if (d.type === "FunctionDeclaration" || d.type === "ClassDeclaration") {
279028
+ if (d.id) {
279029
+ names2.push(d.id.name);
279030
+ bindings.push(d.id.name);
279031
+ }
279032
+ }
279033
+ } else {
279034
+ for (const spec of stmt.specifiers) {
279035
+ if (spec.type !== "ExportSpecifier") continue;
279036
+ names2.push(
279037
+ spec.exported.type === "Identifier" ? spec.exported.name : spec.exported.value
279038
+ );
279039
+ if (spec.local.type === "Identifier") bindings.push(spec.local.name);
278300
279040
  }
278301
- } else if (d.type === "FunctionDeclaration" || d.type === "ClassDeclaration") {
278302
- if (d.id) names2.push(d.id.name);
278303
279041
  }
278304
279042
  if (names2.length === 0) continue;
278305
- const blockStart = generatedCommentBlockStart(sidecarSrc, stmt.start);
278306
- if (blockStart === void 0) continue;
278307
- out.push({ names: names2, start: blockStart, end: stmt.end, text: sidecarSrc.slice(blockStart, stmt.end) });
279043
+ out.push({
279044
+ names: names2,
279045
+ bindings,
279046
+ start: blockStart,
279047
+ end: stmt.end,
279048
+ text: sidecarSrc.slice(blockStart, stmt.end)
279049
+ });
278308
279050
  }
278309
279051
  return out;
278310
279052
  }
@@ -278495,12 +279237,38 @@ function collectParamBodyReadTypes(source) {
278495
279237
  const visitFn = (fnName, fnNode, paramNames) => {
278496
279238
  if (paramNames.size === 0) return;
278497
279239
  const byParam = /* @__PURE__ */ new Map();
278498
- const add2 = (pname, field, hint) => {
278499
- if (!byParam.has(pname)) byParam.set(pname, /* @__PURE__ */ new Map());
278500
- const fields = byParam.get(pname);
278501
- if (!fields.has(field)) fields.set(field, []);
278502
- if (hint) fields.get(field).push(hint);
279240
+ const add2 = (pname, path, hint) => {
279241
+ if (path.length === 0) return;
279242
+ let m = byParam.get(pname);
279243
+ if (!m) byParam.set(pname, m = /* @__PURE__ */ new Map());
279244
+ const k = JSON.stringify(path);
279245
+ let e = m.get(k);
279246
+ if (!e) m.set(k, e = { path, hints: [] });
279247
+ if (hint) e.hints.push(hint);
278503
279248
  };
279249
+ const downChainPath = (mem) => {
279250
+ const keys = [];
279251
+ let cur = mem;
279252
+ let param;
279253
+ while (cur) {
279254
+ if (cur.type !== "MemberExpression" && cur.type !== "OptionalMemberExpression" || cur.computed === true) {
279255
+ return void 0;
279256
+ }
279257
+ const key = keyOf(cur.property);
279258
+ if (key === void 0) return void 0;
279259
+ keys.unshift(key);
279260
+ const obj2 = cur.object;
279261
+ if (!obj2) return void 0;
279262
+ if (obj2.type === "Identifier") {
279263
+ param = obj2.name;
279264
+ break;
279265
+ }
279266
+ cur = obj2;
279267
+ }
279268
+ if (param === void 0) return void 0;
279269
+ return { path: keys, param };
279270
+ };
279271
+ const synthMemberParent = (prop) => ({ type: "MemberExpression", property: { type: "Identifier", name: prop } });
278504
279272
  const walk = (node, shadowed, parent, parentKey) => {
278505
279273
  if (!node || typeof node !== "object") return;
278506
279274
  const n = node;
@@ -278512,29 +279280,31 @@ function collectParamBodyReadTypes(source) {
278512
279280
  const arg = n.argument;
278513
279281
  const mem = arg;
278514
279282
  if (mem && (mem.type === "MemberExpression" || mem.type === "OptionalMemberExpression")) {
278515
- const obj2 = mem.object;
278516
- const prop = mem.property;
278517
- if (obj2?.type === "Identifier" && paramNames.has(obj2.name) && !shadowed.has(obj2.name) && prop && mem.computed !== true) {
278518
- const key = keyOf(prop);
278519
- const pname = obj2.name;
278520
- if (key !== void 0) {
278521
- const gp = parent;
278522
- add2(pname, key, hintFromTypeof(n, gp));
278523
- }
279283
+ const chain = downChainPath(mem);
279284
+ if (chain && paramNames.has(chain.param) && !shadowed.has(chain.param)) {
279285
+ const gp = parent;
279286
+ add2(chain.param, chain.path, hintFromTypeof(n, gp));
278524
279287
  }
278525
279288
  }
278526
279289
  }
278527
279290
  if (n.type === "MemberExpression" || n.type === "OptionalMemberExpression") {
278528
- const obj2 = n.object;
278529
- const prop = n.property;
278530
- const computed = n.computed === true;
278531
- if (obj2?.type === "Identifier" && paramNames.has(obj2.name) && !shadowed.has(obj2.name) && prop && !computed) {
278532
- const key = keyOf(prop);
278533
- const pname = obj2.name;
278534
- if (key !== void 0) {
278535
- const hint = hintFromUse(parent, parentKey ?? "object");
278536
- add2(pname, key, hint);
279291
+ const innerLink = !!parent && (parent.type === "MemberExpression" || parent.type === "OptionalMemberExpression") && parentKey === "object" && parent.computed !== true;
279292
+ if (innerLink) return;
279293
+ const chain = downChainPath(n);
279294
+ if (chain && paramNames.has(chain.param) && !shadowed.has(chain.param)) {
279295
+ const rev = chain.path;
279296
+ let leafLen = rev.length;
279297
+ let leafHint;
279298
+ for (let i = 0; i < rev.length; i++) {
279299
+ const nextProp = i + 1 < rev.length ? rev[i + 1] : void 0;
279300
+ const h = nextProp !== void 0 ? hintFromUse(synthMemberParent(nextProp), "object") : hintFromUse(parent, parentKey ?? "object");
279301
+ if (h) {
279302
+ leafLen = i + 1;
279303
+ leafHint = h;
279304
+ break;
279305
+ }
278537
279306
  }
279307
+ add2(chain.param, rev.slice(0, leafLen), leafHint);
278538
279308
  }
278539
279309
  }
278540
279310
  for (const k of Object.keys(n)) {
@@ -278550,13 +279320,20 @@ function collectParamBodyReadTypes(source) {
278550
279320
  walk(fnNode, /* @__PURE__ */ new Set());
278551
279321
  if (byParam.size === 0) return;
278552
279322
  const fieldsOf = /* @__PURE__ */ new Map();
278553
- for (const [pname, fieldMap] of byParam) {
278554
- const list = [];
278555
- for (const [field, hints] of fieldMap) {
278556
- list.push({ field, ...mergeHints(hints) });
279323
+ for (const [pname, pathMap] of byParam) {
279324
+ const root = { hints: [], children: /* @__PURE__ */ new Map() };
279325
+ for (const { path, hints } of pathMap.values()) {
279326
+ let node = root;
279327
+ for (const seg of path) {
279328
+ let child = node.children.get(seg);
279329
+ if (!child) {
279330
+ node.children.set(seg, child = { hints: [], children: /* @__PURE__ */ new Map() });
279331
+ }
279332
+ node = child;
279333
+ }
279334
+ node.hints.push(...hints);
278557
279335
  }
278558
- list.sort((a, b) => a.field < b.field ? -1 : 1);
278559
- fieldsOf.set(pname, list);
279336
+ fieldsOf.set(pname, emitFields(root));
278560
279337
  }
278561
279338
  out.set(fnName, fieldsOf);
278562
279339
  };
@@ -278635,6 +279412,24 @@ function collectParamBodyReadTypes(source) {
278635
279412
  }
278636
279413
  return out;
278637
279414
  }
279415
+ function emitFields(trie) {
279416
+ const list = [];
279417
+ for (const [name, child] of [...trie.children.entries()].sort(
279418
+ (a, b) => a[0] < b[0] ? -1 : 1
279419
+ )) {
279420
+ if (child.children.size > 0) {
279421
+ list.push({
279422
+ field: name,
279423
+ type: "shape({ \u2026 })",
279424
+ via: "dereferenced member read (nested shape)",
279425
+ fields: emitFields(child)
279426
+ });
279427
+ } else {
279428
+ list.push({ field: name, ...mergeHints(child.hints) });
279429
+ }
279430
+ }
279431
+ return list;
279432
+ }
278638
279433
  function mergeHints(hints) {
278639
279434
  if (hints.length === 0) return { type: "any()", via: "read (no type evidence)" };
278640
279435
  const types2 = [...new Set(hints.map((h) => h.type))];
@@ -278677,7 +279472,9 @@ function bodyReadFieldsFor(map, fnName, paramName2) {
278677
279472
  }
278678
279473
  function shapeDslFromFields(fields) {
278679
279474
  if (fields.length === 0) return "shape({})";
278680
- const parts = fields.map((f) => `${f.field}: ${f.type}`);
279475
+ const parts = fields.map(
279476
+ (f) => f.fields && f.fields.length > 0 ? `${f.field}: ${shapeDslFromFields(f.fields)}` : `${f.field}: ${f.type}`
279477
+ );
278681
279478
  return `shape({ ${parts.join(", ")} })`;
278682
279479
  }
278683
279480
  function collectParamBodyAccesses(source) {
@@ -279042,8 +279839,8 @@ function toDraftBuilderDsl(display) {
279042
279839
  }
279043
279840
  );
279044
279841
  }
279045
- function draftExportName2(fnName) {
279046
- return fnName.includes(".") ? fnName.replace(/\./g, "_") : fnName;
279842
+ function draftExportName2(fnName, used) {
279843
+ return toJsBindingIdent(fnName.includes(".") ? fnName.replace(/\./g, "_") : fnName, used);
279047
279844
  }
279048
279845
  function draftDsl(entry) {
279049
279846
  const parts = entry.params.filter((p) => p.projected && p.constraint !== void 0).map((p) => {
@@ -279158,6 +279955,7 @@ ${suggestedBodyDsl(e.fn, e.params) ?? ""}`;
279158
279955
  }
279159
279956
  }
279160
279957
  const importList = BUILDERS2.filter((b) => used.has(b)).join(", ");
279958
+ const exportUsed = /* @__PURE__ */ new Set();
279161
279959
  const lines = [
279162
279960
  "// @nudo:draft",
279163
279961
  `// Generated by \`nudo contract --draft\` from ${sanitizeCommentText(filePath)}`,
@@ -279196,7 +279994,7 @@ ${suggestedBodyDsl(e.fn, e.params) ?? ""}`;
279196
279994
  } else if (e.returnEvidence === "symbolic") {
279197
279995
  lines.push(`// returns: ${sanitizeCommentText(e.returns?.display ?? "")} (symbolic)`);
279198
279996
  }
279199
- lines.push(`export const ${draftExportName2(e.fn)} = ${e.dsl};`);
279997
+ lines.push(`export const ${draftExportName2(e.fn, exportUsed)} = ${e.dsl};`);
279200
279998
  if (e.fn.includes(".")) {
279201
279999
  lines.push(
279202
280000
  `// sidecar key may also be written as \`${sanitizeCommentText(e.fn)}\` / nested { ${sanitizeCommentText(e.fn.split(".")[1] ?? "")}: \u2026 }`
@@ -279875,7 +280673,8 @@ function formatDerivedSection(row, opts) {
279875
280673
  retDsl = `, ${row.returns.dsl}`;
279876
280674
  }
279877
280675
  const namer = new NameAllocator(opts.takenNames);
279878
- namer.claim(row.fn);
280676
+ const directBinding = directBindingOk(row.fn, (n) => namer.has(n));
280677
+ const binding = directBinding ? namer.claim(row.fn) : namer.claim(allocNudoBinding((n) => namer.has(n)));
279879
280678
  const renames = /* @__PURE__ */ new Map();
279880
280679
  const importLocals = [];
279881
280680
  for (const [name, from] of [...importMap.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
@@ -279906,11 +280705,13 @@ function formatDerivedSection(row, opts) {
279906
280705
  const spec = original === local ? local : `${original} as ${local}`;
279907
280706
  return `import { ${spec} } from ${JSON.stringify(from)};`;
279908
280707
  }).sort((a, b) => a.localeCompare(b));
280708
+ const exportStmt = directBinding ? `export const ${binding} = fn({ ${paramParts.join(", ")} }${retPart});` : `const ${binding} = fn({ ${paramParts.join(", ")} }${retPart});
280709
+ export { ${binding} as ${exportNameRepr(row.fn)} };`;
279909
280710
  const lines = [
279910
280711
  ...importLineTexts,
279911
280712
  ...importLineTexts.length > 0 && renamedPreludes.length > 0 ? [""] : [],
279912
280713
  ...renamedPreludes,
279913
- `export const ${row.fn} = fn({ ${paramParts.join(", ")} }${retPart});`
280714
+ exportStmt
279914
280715
  ];
279915
280716
  const usedNames = /* @__PURE__ */ new Set();
279916
280717
  for (const { local } of importLocals) usedNames.add(local);
@@ -279918,7 +280719,7 @@ function formatDerivedSection(row, opts) {
279918
280719
  const n = preludeLocalName(line);
279919
280720
  if (n) usedNames.add(n);
279920
280721
  }
279921
- usedNames.add(row.fn);
280722
+ usedNames.add(binding);
279922
280723
  return { text: lines.join("\n"), usedNames: [...usedNames] };
279923
280724
  }
279924
280725
  function collectTopLevelNames(src) {
@@ -280168,18 +280969,35 @@ function collectGeneratedSectionsRaw(src) {
280168
280969
  if (stmt.type !== "ExportNamedDeclaration" || stmt.source) continue;
280169
280970
  if (stmt.start == null || stmt.end == null || stmt.start < headerPos) continue;
280170
280971
  const d = stmt.declaration;
280171
- if (!d) continue;
280172
280972
  const names2 = [];
280173
- if (d.type === "VariableDeclaration") {
280174
- for (const decl of d.declarations) {
280175
- if (decl.id.type === "Identifier") names2.push(decl.id.name);
280973
+ const bindings = [];
280974
+ if (d) {
280975
+ if (d.type === "VariableDeclaration") {
280976
+ for (const decl of d.declarations) {
280977
+ if (decl.id.type === "Identifier") {
280978
+ names2.push(decl.id.name);
280979
+ bindings.push(decl.id.name);
280980
+ }
280981
+ }
280982
+ } else if (d.type === "FunctionDeclaration" || d.type === "ClassDeclaration") {
280983
+ if (d.id) {
280984
+ names2.push(d.id.name);
280985
+ bindings.push(d.id.name);
280986
+ }
280987
+ }
280988
+ } else {
280989
+ for (const spec of stmt.specifiers) {
280990
+ if (spec.type !== "ExportSpecifier") continue;
280991
+ names2.push(
280992
+ spec.exported.type === "Identifier" ? spec.exported.name : spec.exported.value
280993
+ );
280994
+ if (spec.local.type === "Identifier") bindings.push(spec.local.name);
280176
280995
  }
280177
- } else if (d.type === "FunctionDeclaration" || d.type === "ClassDeclaration") {
280178
- if (d.id) names2.push(d.id.name);
280179
280996
  }
280180
280997
  if (names2.length === 0) continue;
280181
280998
  out.push({
280182
280999
  names: names2,
281000
+ bindings,
280183
281001
  start: headerPos,
280184
281002
  end: stmt.end,
280185
281003
  text: src.slice(headerPos, stmt.end)
@@ -280201,9 +281019,6 @@ function findGeneratedHeaderOffsets(src) {
280201
281019
  }
280202
281020
  return out;
280203
281021
  }
280204
- function generatedSectionNames(src) {
280205
- return new Set(collectGeneratedSectionsRaw(src).flatMap((s) => s.names));
280206
- }
280207
281022
  function handwrittenNames(src) {
280208
281023
  if (src.trim() === "") return /* @__PURE__ */ new Set();
280209
281024
  let ast;
@@ -280236,7 +281051,10 @@ function handwrittenNames(src) {
280236
281051
  collect(stmt);
280237
281052
  }
280238
281053
  }
280239
- for (const n of generatedSectionNames(src)) names2.delete(n);
281054
+ for (const s of collectGeneratedSectionsRaw(src)) {
281055
+ for (const n of s.names) names2.delete(n);
281056
+ for (const b of s.bindings) names2.delete(b);
281057
+ }
280240
281058
  return names2;
280241
281059
  }
280242
281060
  function findGeneratedSectionText(src, fn2) {
@@ -280289,32 +281107,53 @@ function tsTypeParamName(id) {
280289
281107
  if (n.length === 0) n = "T";
280290
281108
  return n;
280291
281109
  }
280292
- function wrapComplexAbs(a, typeVars) {
280293
- const ts3 = absToTSType(a, typeVars);
281110
+ function wrapComplexAbs(a, typeVars, budget) {
281111
+ const ts3 = absToTSTypeB(a, typeVars, budget);
280294
281112
  if (a.shape.k === "sum" || a.shape.k === "fn") return `(${ts3})`;
280295
281113
  return ts3;
280296
281114
  }
280297
- function wrapUnionMember(a, typeVars) {
280298
- const ts3 = absToTSType(a, typeVars);
281115
+ function wrapUnionMember(a, typeVars, budget) {
281116
+ const ts3 = absToTSTypeB(a, typeVars, budget);
280299
281117
  if (a.shape.k === "fn") return `(${ts3})`;
280300
281118
  return ts3;
280301
281119
  }
280302
- function isTsIdent(name) {
280303
- return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) && !TS_PARAM_RESERVED.has(name);
281120
+ function tsTplSafe(ts3) {
281121
+ if (ts3 === TS_TRUNC.cycle) return TS_TRUNC.cycleTpl;
281122
+ if (ts3 === TS_TRUNC.depth) return TS_TRUNC.depthTpl;
281123
+ return ts3;
280304
281124
  }
280305
281125
  function sanitizeParamName(name, index2) {
280306
- if (name.startsWith("...")) {
280307
- const rest = name.slice(3);
280308
- if (isTsIdent(rest)) return name;
281126
+ let optional = false;
281127
+ let base = name;
281128
+ if (base.endsWith("?")) {
281129
+ optional = true;
281130
+ base = base.slice(0, -1);
281131
+ }
281132
+ if (base.startsWith("...")) {
281133
+ const rest = base.slice(3);
281134
+ if (isJsBindingIdent(rest)) return `...${rest}${optional ? "?" : ""}`;
280309
281135
  return `...arg${index2}`;
280310
281136
  }
280311
- if (isTsIdent(name)) return name;
281137
+ if (isJsBindingIdent(base)) return `${base}${optional ? "?" : ""}`;
280312
281138
  return `arg${index2}`;
280313
281139
  }
280314
281140
  function formatPropKey(k) {
280315
281141
  return formatObjectKey(k);
280316
281142
  }
280317
281143
  function absToTSType(a, typeVars) {
281144
+ return absToTSTypeB(a, typeVars, new ProjectionBudget());
281145
+ }
281146
+ function absToTSTypeB(a, typeVars, budget) {
281147
+ const stop2 = budget.enter(a);
281148
+ if (stop2 === "cycle") return TS_TRUNC.cycle;
281149
+ if (stop2 === "depth") return TS_TRUNC.depth;
281150
+ try {
281151
+ return absToTSTypeInner(a, typeVars, budget);
281152
+ } finally {
281153
+ budget.exit();
281154
+ }
281155
+ }
281156
+ function absToTSTypeInner(a, typeVars, budget) {
280318
281157
  if (a.shape.k === "any" && a.term?.op === "var" && typeVars) {
280319
281158
  const mapped = typeVars.get(a.term.id);
280320
281159
  if (mapped) return mapped;
@@ -280342,7 +281181,7 @@ function absToTSType(a, typeVars) {
280342
281181
  const lvR = litValue(p);
280343
281182
  const lv = lvR.ok ? lvR.value : void 0;
280344
281183
  if (typeof lv === "string") return escapeTemplateTypeFixed(lv);
280345
- return `\${${absToTSType(p, typeVars)}}`;
281184
+ return `\${${tsTplSafe(absToTSTypeB(p, typeVars, budget))}}`;
280346
281185
  }).join("");
280347
281186
  return `\`${inner}\``;
280348
281187
  }
@@ -280356,9 +281195,9 @@ function absToTSType(a, typeVars) {
280356
281195
  return a.shape.type;
280357
281196
  case "obj": {
280358
281197
  const entries = Object.entries(a.shape.slots).map(([k, slot]) => {
280359
- const inner = absToTSType(slot.value, typeVars);
281198
+ const inner = absToTSTypeB(slot.value, typeVars, budget);
280360
281199
  if (slot.optional) {
280361
- const t = wrapComplexAbs(slot.value, typeVars);
281200
+ const t = wrapComplexAbs(slot.value, typeVars, budget);
280362
281201
  return `${formatPropKey(k)}: ${t} | undefined`;
280363
281202
  }
280364
281203
  return `${formatPropKey(k)}: ${inner}`;
@@ -280367,16 +281206,16 @@ function absToTSType(a, typeVars) {
280367
281206
  return `{ ${entries.join("; ")} }`;
280368
281207
  }
280369
281208
  case "arr":
280370
- return `${wrapComplexAbs(a.shape.element, typeVars)}[]`;
281209
+ return `${wrapComplexAbs(a.shape.element, typeVars, budget)}[]`;
280371
281210
  case "tuple": {
280372
281211
  const holes = a.shape.holes ?? [];
280373
281212
  const parts = a.shape.elements.map((e, i) => {
280374
- const ts3 = absToTSType(e, typeVars);
281213
+ const ts3 = absToTSTypeB(e, typeVars, budget);
280375
281214
  return holes.includes(i) ? `hole: ${ts3}` : ts3;
280376
281215
  });
280377
281216
  if (a.shape.rest) {
280378
281217
  const rest = a.shape.rest;
280379
- const restTs = rest.shape.k === "arr" ? absToTSType(rest, typeVars) : `${wrapComplexAbs(rest, typeVars)}[]`;
281218
+ const restTs = rest.shape.k === "arr" ? absToTSTypeB(rest, typeVars, budget) : `${wrapComplexAbs(rest, typeVars, budget)}[]`;
280380
281219
  parts.push(`...${restTs}`);
280381
281220
  }
280382
281221
  return `[${parts.join(", ")}]`;
@@ -280388,22 +281227,27 @@ function absToTSType(a, typeVars) {
280388
281227
  const name = sanitizeParamName(p, i);
280389
281228
  const pt = paramTypes?.[i];
280390
281229
  let typeStr;
280391
- if (pt) typeStr = absToTSType(pt, typeVars);
280392
- else if (isRest) typeStr = "unknown[]";
281230
+ if (pt) {
281231
+ if (isRest) {
281232
+ typeStr = pt.shape.k === "arr" ? absToTSTypeB(pt, typeVars, budget) : `${wrapComplexAbs(pt, typeVars, budget)}[]`;
281233
+ } else {
281234
+ typeStr = absToTSTypeB(pt, typeVars, budget);
281235
+ }
281236
+ } else if (isRest) typeStr = "unknown[]";
280393
281237
  else typeStr = "unknown";
280394
281238
  return `${name}: ${typeStr}`;
280395
281239
  }).join(", ");
280396
- const ret = a.shape.returnType ? absToTSType(a.shape.returnType, typeVars) : "unknown";
281240
+ const ret = a.shape.returnType ? absToTSTypeB(a.shape.returnType, typeVars, budget) : "unknown";
280397
281241
  return `(${params}) => ${ret}`;
280398
281242
  }
280399
281243
  case "brand":
280400
- return isTsIdent(a.shape.name) || /^[A-Z][A-Za-z0-9_$]*$/.test(a.shape.name) ? a.shape.name : "unknown";
281244
+ return isJsBindingIdent(a.shape.name) || /^[A-Z][A-Za-z0-9_$]*$/.test(a.shape.name) ? a.shape.name : "unknown";
280401
281245
  case "eff":
280402
281246
  if (a.shape.eff === "promise")
280403
- return `Promise<${absToTSType(a.shape.inner, typeVars)}>`;
280404
- return absToTSType(a.shape.inner, typeVars);
281247
+ return `Promise<${absToTSTypeB(a.shape.inner, typeVars, budget)}>`;
281248
+ return absToTSTypeB(a.shape.inner, typeVars, budget);
280405
281249
  case "sum": {
280406
- const parts = a.shape.members.map((m) => wrapUnionMember(m, typeVars)).filter((p) => p !== "never");
281250
+ const parts = a.shape.members.map((m) => wrapUnionMember(m, typeVars, budget)).filter((p) => p !== "never");
280407
281251
  const uniq = [...new Set(parts)];
280408
281252
  if (uniq.length === 0) return "never";
280409
281253
  if (uniq.length === 1) return uniq[0];
@@ -280419,32 +281263,47 @@ function caseArgAbs(c, i) {
280419
281263
  function caseResultAbs(c) {
280420
281264
  return c.abs;
280421
281265
  }
280422
- function widenParamAbs(a) {
281266
+ function widenParamAbs(a, budget = new ProjectionBudget()) {
281267
+ const stop2 = budget.enter(a);
281268
+ if (stop2) return a;
281269
+ try {
281270
+ return widenParamAbsInner(a, budget);
281271
+ } finally {
281272
+ budget.exit();
281273
+ }
281274
+ }
281275
+ function widenParamAbsInner(a, budget) {
280423
281276
  const s = a.shape;
280424
281277
  switch (s.k) {
280425
281278
  case "prim":
280426
281279
  return abs(s, void 0, void 0, "exact");
280427
281280
  case "tuple": {
280428
- const widened = s.elements.map(widenParamAbs);
281281
+ const widened = s.elements.map((el) => widenParamAbs(el, budget));
281282
+ const widenedRest = s.rest ? widenParamAbs(s.rest, budget) : void 0;
280429
281283
  const holes = s.holes;
280430
281284
  const first = widened[0];
280431
- if (!holes?.length && first && widened.length > 0 && widened.every((el) => absToTSType(el) === absToTSType(first))) {
281285
+ if (!holes?.length && first && widened.length > 0 && widened.every((el) => absToTSType(el) === absToTSType(first)) && (!widenedRest || absToTSType(widenedRest) === absToTSType(first))) {
280432
281286
  return abs({ k: "arr", element: first }, void 0, void 0, "exact");
280433
281287
  }
280434
281288
  return abs(
280435
- { k: "tuple", elements: widened, ...holes?.length ? { holes: [...holes] } : {} },
281289
+ {
281290
+ k: "tuple",
281291
+ elements: widened,
281292
+ ...widenedRest ? { rest: widenedRest } : {},
281293
+ ...holes?.length ? { holes: [...holes] } : {}
281294
+ },
280436
281295
  void 0,
280437
281296
  void 0,
280438
281297
  "exact"
280439
281298
  );
280440
281299
  }
280441
281300
  case "arr":
280442
- return abs({ k: "arr", element: widenParamAbs(s.element) }, void 0, void 0, "exact");
281301
+ return abs({ k: "arr", element: widenParamAbs(s.element, budget) }, void 0, void 0, "exact");
280443
281302
  case "obj": {
280444
281303
  const slots = {};
280445
281304
  for (const [k, slot] of Object.entries(s.slots)) {
280446
281305
  slots[k] = {
280447
- value: widenParamAbs(slot.value),
281306
+ value: widenParamAbs(slot.value, budget),
280448
281307
  ...slot.optional ? { optional: true } : {}
280449
281308
  };
280450
281309
  }
@@ -280452,21 +281311,21 @@ function widenParamAbs(a) {
280452
281311
  }
280453
281312
  case "eff":
280454
281313
  return abs(
280455
- { k: "eff", eff: s.eff, inner: widenParamAbs(s.inner) },
281314
+ { k: "eff", eff: s.eff, inner: widenParamAbs(s.inner, budget) },
280456
281315
  void 0,
280457
281316
  void 0,
280458
281317
  "exact"
280459
281318
  );
280460
281319
  case "brand":
280461
281320
  return abs(
280462
- { k: "brand", name: s.name, shape: widenParamAbs(s.shape) },
281321
+ { k: "brand", name: s.name, shape: widenParamAbs(s.shape, budget) },
280463
281322
  void 0,
280464
281323
  void 0,
280465
281324
  "exact"
280466
281325
  );
280467
281326
  case "sum":
280468
281327
  return abs(
280469
- { k: "sum", members: s.members.map(widenParamAbs) },
281328
+ { k: "sum", members: s.members.map((m) => widenParamAbs(m, budget)) },
280470
281329
  void 0,
280471
281330
  void 0,
280472
281331
  "exact"
@@ -280543,7 +281402,7 @@ function computeMainSignature(fn2) {
280543
281402
  let name = getParamName(fn2, i);
280544
281403
  const isRest = name.startsWith("...");
280545
281404
  const bare = isRest ? name.slice(3) : name;
280546
- if (!isTsIdent(bare)) {
281405
+ if (!isJsBindingIdent(bare)) {
280547
281406
  name = isRest ? `...arg${i}` : `arg${i}`;
280548
281407
  }
280549
281408
  if (usedNames.has(name)) {
@@ -280616,7 +281475,7 @@ function computeHofSignature(fn2) {
280616
281475
  const typeParams = [];
280617
281476
  for (const id of [...free].sort()) {
280618
281477
  let n = tsTypeParamName(id);
280619
- if (used.has(n) || TS_PARAM_RESERVED.has(n)) {
281478
+ if (used.has(n) || !isJsBindingIdent(n)) {
280620
281479
  let i = 2;
280621
281480
  while (used.has(`${n}${i}`)) i++;
280622
281481
  n = `${n}${i}`;
@@ -280745,6 +281604,7 @@ function numericBound2(p, self, allowSelfVar) {
280745
281604
  const nR = litOf(p.b);
280746
281605
  const n = nR.ok ? nR.value : void 0;
280747
281606
  if (typeof n !== "number") return "drop";
281607
+ if (!Number.isFinite(n)) return "drop";
280748
281608
  if (p.a.op === "app" && p.a.fn === "length") return "skip";
280749
281609
  if (allowSelfVar && isSelfVar2(p.a, self)) return { op: p.op, n };
280750
281610
  if (p.a.op === "app" && (p.a.fn === "get" || p.a.fn === "length")) return "skip";
@@ -280757,6 +281617,7 @@ function lengthBound3(p, self) {
280757
281617
  const nR = litOf(p.b);
280758
281618
  const n = nR.ok ? nR.value : void 0;
280759
281619
  if (typeof n !== "number") return void 0;
281620
+ if (!Number.isFinite(n)) return void 0;
280760
281621
  if (p.op === "ge") return { dir: "min", n: Math.ceil(n) };
280761
281622
  if (p.op === "gt") return { dir: "min", n: Math.floor(n) + 1 };
280762
281623
  if (p.op === "le") return { dir: "max", n: Math.floor(n) };
@@ -280817,7 +281678,7 @@ function refinementsFromPreds(preds, opts) {
280817
281678
  }
280818
281679
  dropped.push(`pred not projected: ${predToString(p)}`);
280819
281680
  }
280820
- return { refinements, ...eqLit !== void 0 ? { eqLit } : {}, dropped };
281681
+ return { refinements, ...hasEqLit ? { eqLit } : {}, hasEqLit, dropped };
280821
281682
  }
280822
281683
  function constraintToSchemaNode(c) {
280823
281684
  if (c.fn) return { k: "fn" };
@@ -280839,11 +281700,11 @@ function constraintToSchemaNode(c) {
280839
281700
  }
280840
281701
  const preds = c.preds ?? [];
280841
281702
  const kind = c.prim === "number" ? "number" : c.prim === "string" ? "string" : c.prim === "boolean" ? "boolean" : c.prim ? "other" : "other";
280842
- const { refinements, eqLit, dropped } = refinementsFromPreds(preds, {
281703
+ const { refinements, eqLit, hasEqLit, dropped } = refinementsFromPreds(preds, {
280843
281704
  kind
280844
281705
  // constraint 路径:absToConstraint 已把 self 改写成 __nudo_self__
280845
281706
  });
280846
- if (eqLit !== void 0 && (kind === "number" || kind === "string" || kind === "boolean" || !c.prim)) {
281707
+ if (hasEqLit && (kind === "number" || kind === "string" || kind === "boolean" || !c.prim)) {
280847
281708
  return { k: "lit", value: eqLit };
280848
281709
  }
280849
281710
  if (isIntFlag(c) && !refinements.some((r) => r.kind === "int")) {
@@ -280876,17 +281737,18 @@ function constraintDropped(c, prefix2 = "") {
280876
281737
  }
280877
281738
  if (n.fn) return;
280878
281739
  const kind = n.prim === "number" ? "number" : n.prim === "string" ? "string" : n.prim === "boolean" ? "boolean" : "other";
280879
- const { eqLit, dropped } = refinementsFromPreds(n.preds ?? [], {
281740
+ const { dropped } = refinementsFromPreds(n.preds ?? [], {
280880
281741
  kind
280881
281742
  });
280882
281743
  for (const d of dropped) out.push(path ? `${path}: ${d}` : d);
280883
- if (eqLit === void 0 && n.preds?.some((p) => p.op === "eq") && !isIntFlag(n)) {
280884
- }
280885
281744
  };
280886
281745
  visit2(c, prefix2);
280887
281746
  return out;
280888
281747
  }
280889
281748
  function absToSchemaNode(a) {
281749
+ return absToSchemaNodeB(a, new ProjectionBudget());
281750
+ }
281751
+ function absToSchemaNodeB(a, budget) {
280890
281752
  const dropped = [];
280891
281753
  if (a.term?.op === "lit") {
280892
281754
  const v2 = a.term.value;
@@ -280901,7 +281763,7 @@ function absToSchemaNode(a) {
280901
281763
  }
280902
281764
  const conf = a.conf;
280903
281765
  const lowConf = conf !== "exact" && conf !== "path";
280904
- const projected = absToConstraint(a);
281766
+ const projected = absToConstraint(a, budget);
280905
281767
  if (projected) {
280906
281768
  const node = constraintToSchemaNode(projected);
280907
281769
  dropped.push(...constraintDropped(projected));
@@ -280910,6 +281772,18 @@ function absToSchemaNode(a) {
280910
281772
  if (lowConf) {
280911
281773
  dropped.push(`conf=${conf}: contract projection skipped; shape-only fallback`);
280912
281774
  }
281775
+ const stop2 = budget.enter(a);
281776
+ if (stop2) {
281777
+ dropped.push(`shape truncated (${stop2}) projected as unknown`);
281778
+ return { node: { k: "unknown" }, dropped };
281779
+ }
281780
+ try {
281781
+ return absToSchemaShape(a, budget, dropped);
281782
+ } finally {
281783
+ budget.exit();
281784
+ }
281785
+ }
281786
+ function absToSchemaShape(a, budget, dropped) {
280913
281787
  const s = a.shape;
280914
281788
  switch (s.k) {
280915
281789
  case "never":
@@ -280926,12 +281800,12 @@ function absToSchemaNode(a) {
280926
281800
  }
280927
281801
  return { node: { k: "prim", type: s.type, refinements: [] }, dropped };
280928
281802
  }
280929
- const { refinements, eqLit, dropped: d } = refinementsFromPreds(leaves, {
281803
+ const { refinements, eqLit, hasEqLit, dropped: d } = refinementsFromPreds(leaves, {
280930
281804
  kind,
280931
281805
  self: a.term
280932
281806
  });
280933
281807
  dropped.push(...d);
280934
- if (eqLit !== void 0 && !Number.isNaN(eqLit)) {
281808
+ if (hasEqLit && (eqLit === void 0 || !Number.isNaN(eqLit))) {
280935
281809
  return { node: { k: "lit", value: eqLit }, dropped };
280936
281810
  }
280937
281811
  return { node: { k: "prim", type: s.type, refinements }, dropped };
@@ -280945,7 +281819,7 @@ function absToSchemaNode(a) {
280945
281819
  }
280946
281820
  const slots = [];
280947
281821
  for (const [key, slot] of Object.entries(s.slots)) {
280948
- const sub2 = absToSchemaNode(slot.value);
281822
+ const sub2 = absToSchemaNodeB(slot.value, budget);
280949
281823
  dropped.push(...sub2.dropped.map((n) => `${key}: ${n}`));
280950
281824
  slots.push({ key, node: sub2.node, ...slot.optional ? { optional: true } : {} });
280951
281825
  }
@@ -280955,7 +281829,7 @@ function absToSchemaNode(a) {
280955
281829
  if (a.pred && a.pred.op !== "true") {
280956
281830
  dropped.push(`arr pred not projected: ${predToString(a.pred)}`);
280957
281831
  }
280958
- const sub2 = absToSchemaNode(s.element);
281832
+ const sub2 = absToSchemaNodeB(s.element, budget);
280959
281833
  dropped.push(...sub2.dropped);
280960
281834
  return { node: { k: "arr", element: sub2.node }, dropped };
280961
281835
  }
@@ -280966,15 +281840,21 @@ function absToSchemaNode(a) {
280966
281840
  dropped.push(`tuple hole at ${i} not projected (slot absent \u2260 undefined)`);
280967
281841
  return { k: "unknown" };
280968
281842
  }
280969
- const sub2 = absToSchemaNode(e);
281843
+ const sub2 = absToSchemaNodeB(e, budget);
280970
281844
  dropped.push(...sub2.dropped);
280971
281845
  return sub2.node;
280972
281846
  });
280973
- return { node: { k: "tuple", elements }, dropped };
281847
+ let rest;
281848
+ if (s.rest) {
281849
+ const sub2 = absToSchemaNodeB(s.rest, budget);
281850
+ dropped.push(...sub2.dropped);
281851
+ rest = sub2.node;
281852
+ }
281853
+ return { node: { k: "tuple", elements, ...rest ? { rest } : {} }, dropped };
280974
281854
  }
280975
281855
  case "sum": {
280976
281856
  const members = s.members.map((m) => {
280977
- const sub2 = absToSchemaNode(m);
281857
+ const sub2 = absToSchemaNodeB(m, budget);
280978
281858
  dropped.push(...sub2.dropped);
280979
281859
  return sub2.node;
280980
281860
  });
@@ -280983,7 +281863,7 @@ function absToSchemaNode(a) {
280983
281863
  case "fn":
280984
281864
  return { node: { k: "fn" }, dropped };
280985
281865
  case "eff": {
280986
- const inner = absToSchemaNode(s.inner);
281866
+ const inner = absToSchemaNodeB(s.inner, budget);
280987
281867
  dropped.push(...inner.dropped);
280988
281868
  return {
280989
281869
  node: s.eff === "promise" ? { k: "promise", inner: inner.node } : inner.node,
@@ -281042,8 +281922,10 @@ function schemaNodeToZod(node) {
281042
281922
  }
281043
281923
  case "arr":
281044
281924
  return `z.array(${schemaNodeToZod(node.element)})`;
281045
- case "tuple":
281046
- return `z.tuple([${node.elements.map(schemaNodeToZod).join(", ")}])`;
281925
+ case "tuple": {
281926
+ const base = `z.tuple([${node.elements.map(schemaNodeToZod).join(", ")}])`;
281927
+ return node.rest ? `${base}.rest(${schemaNodeToZod(node.rest)})` : base;
281928
+ }
281047
281929
  case "union":
281048
281930
  return `z.union([${node.members.map(schemaNodeToZod).join(", ")}])`;
281049
281931
  case "fn":
@@ -281068,24 +281950,23 @@ function projectAbsToSchema(a, opts) {
281068
281950
  function absToSchemaSource(a, opts) {
281069
281951
  return projectAbsToSchema(a, opts).source;
281070
281952
  }
281071
- function zodExportIdent(name) {
281072
- if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name)) return name;
281073
- const cleaned = name.replace(/[^A-Za-z0-9_$]/g, "_");
281074
- return /^[A-Za-z_$]/.test(cleaned) ? cleaned : `_${cleaned}`;
281953
+ function zodExportIdent(name, used) {
281954
+ return toJsBindingIdent(name, used);
281075
281955
  }
281076
281956
  function formatJsObjectKey(k) {
281077
281957
  return formatObjectKey(k);
281078
281958
  }
281079
- function safeExportIdent(name) {
281080
- return zodExportIdent(name);
281959
+ function safeExportIdent(name, used) {
281960
+ return zodExportIdent(name, used);
281081
281961
  }
281082
281962
  function absToZodSchemaModule(exports, opts) {
281083
281963
  const dropped = [];
281084
281964
  const decls = [];
281965
+ const used = /* @__PURE__ */ new Set();
281085
281966
  for (const [name, abs2] of Object.entries(exports)) {
281086
281967
  const p = projectAbsToSchema(abs2, { dialect: "zod" });
281087
281968
  dropped.push(...p.dropped.map((n) => `${name}: ${n}`));
281088
- decls.push(`export const ${zodExportIdent(name)} = ${p.source};`);
281969
+ decls.push(`export const ${zodExportIdent(name, used)} = ${p.source};`);
281089
281970
  }
281090
281971
  const banner = opts?.banner ?? `// @generated by nudo export --format schema \u2014 dialect: zod
281091
281972
  // One-way lossy projection of Abs; do not edit. nudo check remains the gate.`;
@@ -281179,10 +282060,11 @@ function checkNode(node, value, path, issues) {
281179
282060
  const rec = value;
281180
282061
  for (const slot of node.slots) {
281181
282062
  const key = slot.key;
281182
- if (!(key in rec) || rec[key] === void 0) {
282063
+ if (!Object.hasOwn(rec, key)) {
281183
282064
  if (!slot.optional) pushIssue(issues, [...path, key], "required");
281184
282065
  continue;
281185
282066
  }
282067
+ if (rec[key] === void 0 && slot.optional) continue;
281186
282068
  checkNode(slot.node, rec[key], [...path, key], issues);
281187
282069
  }
281188
282070
  return;
@@ -281205,6 +282087,11 @@ function checkNode(node, value, path, issues) {
281205
282087
  node.elements.forEach((el, i) => {
281206
282088
  checkNode(el, value[i], [...path, i], issues);
281207
282089
  });
282090
+ if (node.rest) {
282091
+ for (let i = node.elements.length; i < value.length; i++) {
282092
+ checkNode(node.rest, value[i], [...path, i], issues);
282093
+ }
282094
+ }
281208
282095
  return;
281209
282096
  }
281210
282097
  case "union": {
@@ -281229,10 +282116,10 @@ function validateSchemaNode(node, value) {
281229
282116
  checkNode(node, value, [], issues);
281230
282117
  return issues.length === 0 ? { value } : { issues };
281231
282118
  }
281232
- function makeStandardSchemaSource(exportName, node, dropped) {
282119
+ function makeStandardSchemaSource(exportName, node, dropped, used) {
281233
282120
  const json = JSON.stringify(node);
281234
282121
  const notes = dropped.length > 0 ? dropped.map((d) => `// ${sanitizeCommentText(d)}`).join("\n") + "\n" : "";
281235
- return `${notes}export const ${safeExportIdent(exportName)} = {
282122
+ return `${notes}export const ${safeExportIdent(exportName, used)} = {
281236
282123
  "~standard": {
281237
282124
  version: 1,
281238
282125
  vendor: "nudo",
@@ -281247,10 +282134,11 @@ function makeStandardSchemaSource(exportName, node, dropped) {
281247
282134
  function absToStandardSchemaModule(exports, opts) {
281248
282135
  const dropped = [];
281249
282136
  const bodies = [];
282137
+ const used = /* @__PURE__ */ new Set();
281250
282138
  for (const [name, abs2] of Object.entries(exports)) {
281251
282139
  const { node, dropped: d } = absToSchemaNode(abs2);
281252
282140
  dropped.push(...d.map((n) => `${name}: ${n}`));
281253
- bodies.push(makeStandardSchemaSource(name, node, d.map((n) => `${name}: ${n}`)));
282141
+ bodies.push(makeStandardSchemaSource(name, node, d.map((n) => `${name}: ${n}`), used));
281254
282142
  }
281255
282143
  const banner = opts?.banner ?? `// @generated by nudo export --format standard \u2014 Standard Schema v1 (vendor: nudo)
281256
282144
  // One-way lossy projection of Abs; do not edit. nudo check remains the gate.`;
@@ -281460,15 +282348,85 @@ ${tag}
281460
282348
  ]
281461
282349
  };
281462
282350
  }
281463
- function wrapReturnNullable(sidecarSource, fnName) {
281464
- const fn2 = escapeRegExp22(fnName);
282351
+ function* exportSpecifierParts(src) {
282352
+ const re = /\bexport\s*\{/g;
282353
+ let m;
282354
+ while ((m = re.exec(src)) !== null) {
282355
+ const parts = [];
282356
+ let cur = "";
282357
+ let quote;
282358
+ let close = -1;
282359
+ for (let i = m.index + m[0].length; i < src.length; i++) {
282360
+ const ch = src[i];
282361
+ if (quote !== void 0) {
282362
+ cur += ch;
282363
+ if (ch === "\\") {
282364
+ if (i + 1 < src.length) cur += src[i + 1];
282365
+ i++;
282366
+ } else if (ch === quote) {
282367
+ quote = void 0;
282368
+ }
282369
+ continue;
282370
+ }
282371
+ if (ch === '"' || ch === "'") {
282372
+ quote = ch;
282373
+ cur += ch;
282374
+ } else if (ch === ",") {
282375
+ parts.push(cur);
282376
+ cur = "";
282377
+ } else if (ch === "}") {
282378
+ parts.push(cur);
282379
+ cur = "";
282380
+ close = i;
282381
+ break;
282382
+ } else {
282383
+ cur += ch;
282384
+ }
282385
+ }
282386
+ if (close === -1) break;
282387
+ re.lastIndex = close + 1;
282388
+ for (const p of parts) yield p.trim();
282389
+ }
282390
+ }
282391
+ function sidecarBindingFor(sidecarSource, exportName) {
282392
+ for (const t of exportSpecifierParts(sidecarSource)) {
282393
+ if (!t) continue;
282394
+ const asMatch = /^([\w$]+)\s+as\s+(.+)$/.exec(t);
282395
+ if (!asMatch) continue;
282396
+ const local = asMatch[1];
282397
+ let exported = asMatch[2].trim();
282398
+ if (exported.startsWith('"') || exported.startsWith("'")) {
282399
+ try {
282400
+ exported = JSON.parse(exported.replace(/^'|'$/g, '"'));
282401
+ } catch {
282402
+ continue;
282403
+ }
282404
+ }
282405
+ if (exported === exportName) return local;
282406
+ }
282407
+ return void 0;
282408
+ }
282409
+ function matchSidecarFnDecl(sidecarSource, exportName) {
282410
+ const fn2 = escapeRegExp22(exportName);
281465
282411
  const startRe = new RegExp(
281466
282412
  `(?:export\\s+const\\s+${fn2}\\s*=\\s*|(?<![\\w$])${fn2}\\s*=\\s*)fn\\s*\\(`
281467
282413
  );
281468
- const m = sidecarSource.match(startRe);
282414
+ let m = sidecarSource.match(startRe);
282415
+ if (!m || m.index === void 0) {
282416
+ const binding = sidecarBindingFor(sidecarSource, exportName);
282417
+ if (binding === void 0) return void 0;
282418
+ m = sidecarSource.match(
282419
+ new RegExp(`(?<![\\w$])${escapeRegExp22(binding)}\\s*=\\s*fn\\s*\\(`)
282420
+ );
282421
+ }
281469
282422
  if (!m || m.index === void 0) return void 0;
281470
- const start = m.index;
281471
- const open = start + m[0].lastIndexOf("(");
282423
+ return { start: m.index, openParen: m.index + m[0].lastIndexOf("(") };
282424
+ }
282425
+ function wrapReturnNullable(sidecarSource, fnName) {
282426
+ const located = matchSidecarFnDecl(sidecarSource, fnName);
282427
+ if (located === void 0) return void 0;
282428
+ const start = located.start;
282429
+ const open = located.openParen;
281472
282430
  let depth = 0;
281473
282431
  let end = -1;
281474
282432
  for (let i = open; i < sidecarSource.length; i++) {
@@ -281587,8 +282545,19 @@ function materializeAction(input) {
281587
282545
  fields = fieldNames.map((f) => ({ field: f, type: "any()", via: "read (no type evidence)" }));
281588
282546
  }
281589
282547
  const shapeText = shapeDslFromFields(fields);
281590
- const clause = `export const ${fn2} = fn({ ${paramName2}: ${shapeText} });
282548
+ const direct = isJsBindingIdent(fn2);
282549
+ const clause = direct ? `export const ${fn2} = fn({ ${paramName2}: ${shapeText} });
282550
+ ` : (() => {
282551
+ const used = new Set(
282552
+ [...(sidecarText ?? "").matchAll(/(?<![\w$])_nudo_(\d+)(?![\w$])/g)].map(
282553
+ (x) => x[0]
282554
+ )
282555
+ );
282556
+ const binding = allocNudoBinding((n) => used.has(n));
282557
+ return `const ${binding} = fn({ ${paramName2}: ${shapeText} });
282558
+ export { ${binding} as ${exportNameRepr(fn2)} };
281591
282559
  `;
282560
+ })();
281592
282561
  const next = sidecarText && sidecarText.trim().length > 0 ? sidecarText.endsWith("\n") ? sidecarText + clause : sidecarText + "\n" + clause : `// Generated by quickfix \u2014 review before accepting
281593
282562
  ${clause}`;
281594
282563
  return {
@@ -281637,17 +282606,19 @@ ${clause}`;
281637
282606
  }
281638
282607
  return void 0;
281639
282608
  }
281640
- var GENERATED_PREFIX, GENERATED_CASE_LINE_REGEX, IDENT_KEY_REGEX, NUMBER_LITERAL_REGEX, UNSAFE_STRING_CHARS, UNSAFE_KEY_CHARS, CONTROL_CHARS, DIFF_CONTEXT, GENERATED_HEADER, STRING_METHODS2, NUMBER_METHODS, ARRAY_METHODS, NameAllocator, DERIVED_HEADER, TS_PARAM_RESERVED, DIALECT_RENDERERS, CHECK_FN_SOURCE;
282609
+ var GENERATED_PREFIX, GENERATED_CASE_LINE_REGEX, IDENT_KEY_REGEX, NUMBER_LITERAL_REGEX, UNSAFE_STRING_CHARS, UNSAFE_KEY_CHARS, CONTROL_CHARS, DIFF_CONTEXT, JS_EXPORT_NAME, GENERATED_HEADER, STRING_METHODS2, NUMBER_METHODS, ARRAY_METHODS, NameAllocator, DERIVED_HEADER, TS_TRUNC, DIALECT_RENDERERS, CHECK_FN_SOURCE;
281641
282610
  var init_emit2 = __esm({
281642
282611
  "../service/dist/emit/index.js"() {
281643
282612
  "use strict";
281644
282613
  init_chunk_4TAFH6ZZ();
281645
- init_chunk_XY75TNBY();
281646
- init_chunk_RHEXBPQ6();
282614
+ init_chunk_6GFPZGQS();
282615
+ init_chunk_2RP2Q6LJ();
281647
282616
  init_src();
281648
282617
  init_src();
281649
282618
  init_internal();
282619
+ init_internal();
281650
282620
  init_src2();
282621
+ init_internal();
281651
282622
  init_src();
281652
282623
  init_internal();
281653
282624
  init_src2();
@@ -281666,6 +282637,7 @@ var init_emit2 = __esm({
281666
282637
  init_internal();
281667
282638
  init_internal();
281668
282639
  init_src();
282640
+ init_internal();
281669
282641
  GENERATED_PREFIX = "call@";
281670
282642
  GENERATED_CASE_LINE_REGEX = /^\s*\*?\s*@nudo:case\s+"(call@[^"]*)"/;
281671
282643
  IDENT_KEY_REGEX = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
@@ -281674,6 +282646,7 @@ var init_emit2 = __esm({
281674
282646
  UNSAFE_KEY_CHARS = /[,()[\]{}:]/;
281675
282647
  CONTROL_CHARS = /[\x00-\x1f\x7f]/;
281676
282648
  DIFF_CONTEXT = 3;
282649
+ JS_EXPORT_NAME = /^(?:[$_\p{ID_Start}])(?:[$\u200C\u200D\p{ID_Continue}])*$/u;
281677
282650
  GENERATED_HEADER = "// @generated by nudo \u2014 do not edit; regenerate with `nudo contract --emit`";
281678
282651
  STRING_METHODS2 = /* @__PURE__ */ new Set([
281679
282652
  "toLowerCase",
@@ -281740,6 +282713,10 @@ var init_emit2 = __esm({
281740
282713
  constructor(initial) {
281741
282714
  this.taken = new Set(initial ?? []);
281742
282715
  }
282716
+ /** 名字是否已被占用(绑定别名分配用) */
282717
+ has(preferred) {
282718
+ return this.taken.has(preferred);
282719
+ }
281743
282720
  claim(preferred) {
281744
282721
  if (!this.taken.has(preferred)) {
281745
282722
  this.taken.add(preferred);
@@ -281753,57 +282730,12 @@ var init_emit2 = __esm({
281753
282730
  }
281754
282731
  };
281755
282732
  DERIVED_HEADER = "// @generated by nudo \u2014 do not edit; regenerate with `nudo contract --emit`";
281756
- TS_PARAM_RESERVED = /* @__PURE__ */ new Set([
281757
- "break",
281758
- "case",
281759
- "catch",
281760
- "class",
281761
- "const",
281762
- "continue",
281763
- "debugger",
281764
- "default",
281765
- "delete",
281766
- "do",
281767
- "else",
281768
- "enum",
281769
- "export",
281770
- "extends",
281771
- "false",
281772
- "finally",
281773
- "for",
281774
- "function",
281775
- "if",
281776
- "import",
281777
- "in",
281778
- "instanceof",
281779
- "new",
281780
- "null",
281781
- "return",
281782
- "super",
281783
- "switch",
281784
- "this",
281785
- "throw",
281786
- "true",
281787
- "try",
281788
- "typeof",
281789
- "var",
281790
- "void",
281791
- "while",
281792
- "with",
281793
- "yield",
281794
- "let",
281795
- "static",
281796
- "await",
281797
- "implements",
281798
- "interface",
281799
- "package",
281800
- "private",
281801
- "protected",
281802
- "public",
281803
- "arguments",
281804
- "eval",
281805
- "constructor"
281806
- ]);
282733
+ TS_TRUNC = {
282734
+ cycle: "/* nudo:truncated:cycle */ unknown",
282735
+ depth: "/* nudo:truncated:depth */ unknown",
282736
+ cycleTpl: "/* nudo:truncated:cycle */ string",
282737
+ depthTpl: "/* nudo:truncated:depth */ string"
282738
+ };
281807
282739
  DIALECT_RENDERERS = {
281808
282740
  zod: schemaNodeToZod
281809
282741
  };
@@ -281870,10 +282802,12 @@ function __nudoCheck(node, value, path, issues) {
281870
282802
  }
281871
282803
  for (const slot of node.slots) {
281872
282804
  const key = slot.key;
281873
- if (!(key in value) || value[key] === undefined) {
282805
+ // \u7F3A\u952E\u5224\u5B9A\u53EA\u7528\u81EA\u6709\u5C5E\u6027\uFF08BUG-018/S6-003\uFF1B\u4E24\u4EFD\u5B9E\u73B0\u987B\u540C\u53E3\u5F84\uFF09
282806
+ if (!Object.hasOwn(value, key)) {
281874
282807
  if (!slot.optional) { path.push(key); push("required"); path.pop(); }
281875
282808
  continue;
281876
282809
  }
282810
+ if (value[key] === undefined && slot.optional) continue;
281877
282811
  path.push(key);
281878
282812
  __nudoCheck(slot.node, value[key], path, issues);
281879
282813
  path.pop();
@@ -281896,6 +282830,13 @@ function __nudoCheck(node, value, path, issues) {
281896
282830
  __nudoCheck(node.elements[i], value[i], path, issues);
281897
282831
  path.pop();
281898
282832
  }
282833
+ if (node.rest) {
282834
+ for (var ri = node.elements.length; ri < value.length; ri++) {
282835
+ path.push(ri);
282836
+ __nudoCheck(node.rest, value[ri], path, issues);
282837
+ path.pop();
282838
+ }
282839
+ }
281899
282840
  return;
281900
282841
  }
281901
282842
  case "union": {
@@ -282089,7 +283030,7 @@ __export(migrate_exports, {
282089
283030
  stripTsToJs: () => stripTsToJs
282090
283031
  });
282091
283032
  import {
282092
- existsSync as existsSync25,
283033
+ existsSync as existsSync26,
282093
283034
  mkdirSync as mkdirSync7,
282094
283035
  readFileSync as readFileSync28,
282095
283036
  readdirSync as readdirSync4,
@@ -282110,7 +283051,7 @@ function readJson(path) {
282110
283051
  }
282111
283052
  function writeFileAtomic(path, data) {
282112
283053
  const dir = dirname27(path);
282113
- if (!existsSync25(dir)) mkdirSync7(dir, { recursive: true });
283054
+ if (!existsSync26(dir)) mkdirSync7(dir, { recursive: true });
282114
283055
  const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
282115
283056
  try {
282116
283057
  writeFileSync10(tmp, data, "utf-8");
@@ -282128,7 +283069,7 @@ function writeJson(path, value) {
282128
283069
  `);
282129
283070
  }
282130
283071
  function snapshotFile(path) {
282131
- if (!existsSync25(path)) return { path, kind: "missing" };
283072
+ if (!existsSync26(path)) return { path, kind: "missing" };
282132
283073
  try {
282133
283074
  if (statSync15(path).isFile()) {
282134
283075
  return { path, kind: "file", content: readFileSync28(path, "utf-8") };
@@ -282140,7 +283081,7 @@ function snapshotFile(path) {
282140
283081
  }
282141
283082
  function restoreSnapshot(snap) {
282142
283083
  if (snap.kind === "missing") {
282143
- if (existsSync25(snap.path)) rmSync3(snap.path, { force: true });
283084
+ if (existsSync26(snap.path)) rmSync3(snap.path, { force: true });
282144
283085
  return;
282145
283086
  }
282146
283087
  if (snap.kind === "file") writeFileAtomic(snap.path, snap.content);
@@ -282237,7 +283178,7 @@ function unquoteYamlScalar(s) {
282237
283178
  }
282238
283179
  function parsePnpmWorkspacePackages(root) {
282239
283180
  const yamlPath = join19(root, "pnpm-workspace.yaml");
282240
- if (!existsSync25(yamlPath)) return [];
283181
+ if (!existsSync26(yamlPath)) return [];
282241
283182
  let text;
282242
283183
  try {
282243
283184
  text = readFileSync28(yamlPath, "utf-8");
@@ -282299,17 +283240,17 @@ function packageRoots(root) {
282299
283240
  if (w.startsWith("!")) continue;
282300
283241
  if (w.endsWith("/*") || w.endsWith("/**")) {
282301
283242
  const base = join19(root, w.replace(/\/\*\*?$/, ""));
282302
- if (!existsSync25(base)) continue;
283243
+ if (!existsSync26(base)) continue;
282303
283244
  for (const name of readdirSync4(base)) {
282304
283245
  const p = join19(base, name);
282305
- if (!existsSync25(join19(p, "package.json"))) continue;
283246
+ if (!existsSync26(join19(p, "package.json"))) continue;
282306
283247
  if (seen.has(p)) continue;
282307
283248
  seen.add(p);
282308
283249
  roots.push(p);
282309
283250
  }
282310
283251
  } else {
282311
283252
  const p = join19(root, w);
282312
- if (!existsSync25(join19(p, "package.json"))) continue;
283253
+ if (!existsSync26(join19(p, "package.json"))) continue;
282313
283254
  if (seen.has(p)) continue;
282314
283255
  seen.add(p);
282315
283256
  roots.push(p);
@@ -282537,7 +283478,7 @@ function findWorkflowDir(from) {
282537
283478
  let cur = resolve19(from);
282538
283479
  for (let i = 0; i < 6; i++) {
282539
283480
  const wf = join19(cur, ".github", "workflows");
282540
- if (existsSync25(wf) && statSync15(wf).isDirectory()) return wf;
283481
+ if (existsSync26(wf) && statSync15(wf).isDirectory()) return wf;
282541
283482
  const parent = dirname27(cur);
282542
283483
  if (parent === cur) break;
282543
283484
  cur = parent;
@@ -282583,7 +283524,7 @@ function rewriteWorkflowsNear(from, opts = {}) {
282583
283524
  }
282584
283525
  function resolveMigrateRoot(rootDir) {
282585
283526
  const raw = resolve19(rootDir);
282586
- if (!existsSync25(raw)) {
283527
+ if (!existsSync26(raw)) {
282587
283528
  throw new Error(`not found: ${rootDir}`);
282588
283529
  }
282589
283530
  return statSync15(raw).isDirectory() ? raw : dirname27(raw);
@@ -282607,9 +283548,9 @@ function migrateStatus(rootDir) {
282607
283548
  ...pkg?.devDependencies ?? {}
282608
283549
  };
282609
283550
  const markerPath = join19(r, ".nudo", "migrate-retired.json");
282610
- const retired = existsSync25(markerPath) && statSync15(markerPath).isFile();
283551
+ const retired = existsSync26(markerPath) && statSync15(markerPath).isFile();
282611
283552
  const retiringPath = join19(r, ".nudo", "migrate-retiring.json");
282612
- const retiring = !retired && existsSync25(retiringPath) && statSync15(retiringPath).isFile();
283553
+ const retiring = !retired && existsSync26(retiringPath) && statSync15(retiringPath).isFile();
282613
283554
  const blockers = [];
282614
283555
  if (retiring) {
282615
283556
  blockers.push(
@@ -282630,9 +283571,9 @@ function migrateStatus(rootDir) {
282630
283571
  }
282631
283572
  rows.push({
282632
283573
  root: relative12(process.cwd(), r) || ".",
282633
- packageJson: existsSync25(pkgPath),
283574
+ packageJson: existsSync26(pkgPath),
282634
283575
  typescriptDep: Boolean(deps.typescript),
282635
- tsconfig: existsSync25(join19(r, "tsconfig.json")),
283576
+ tsconfig: existsSync26(join19(r, "tsconfig.json")),
282636
283577
  tscScripts,
282637
283578
  tsFiles,
282638
283579
  tsxFiles,
@@ -282672,7 +283613,7 @@ async function migrateStrip(paths, opts = {}) {
282672
283613
  const files = [];
282673
283614
  for (const p of paths) {
282674
283615
  const abs2 = resolve19(p);
282675
- if (!existsSync25(abs2)) {
283616
+ if (!existsSync26(abs2)) {
282676
283617
  throw new Error(`not found: ${p}`);
282677
283618
  }
282678
283619
  if (statSync15(abs2).isDirectory()) {
@@ -282696,9 +283637,9 @@ async function migrateStrip(paths, opts = {}) {
282696
283637
  const outFile = TSX_EXT.has(ext) ? file.replace(/\.tsx$/i, ".jsx") : file.replace(/\.tsx$/i, ".js").replace(/\.mts$/i, ".mjs").replace(/\.cts$/i, ".cjs").replace(/\.ts$/i, ".js");
282697
283638
  const wouldClobberNonTs = outFile === file && !isStripSource(file);
282698
283639
  if (opts.write && !wouldClobberNonTs) {
282699
- if (opts.backup && existsSync25(file) && !existsSync25(`${file}.bak`)) {
283640
+ if (opts.backup && existsSync26(file) && !existsSync26(`${file}.bak`)) {
282700
283641
  renameSync4(file, `${file}.bak`);
282701
- } else if (opts.backup !== true && existsSync25(file) && extname(file).toLowerCase() !== extname(outFile).toLowerCase()) {
283642
+ } else if (opts.backup !== true && existsSync26(file) && extname(file).toLowerCase() !== extname(outFile).toLowerCase()) {
282702
283643
  }
282703
283644
  writeFileSync10(outFile, code2, "utf-8");
282704
283645
  }
@@ -282708,7 +283649,7 @@ async function migrateStrip(paths, opts = {}) {
282708
283649
  const draft = await draftInterface(outFile, { source: code2, bodyAccesses: true });
282709
283650
  if (draft.draftSource && draft.draftSource.trim().length > 0) {
282710
283651
  const sc = sidecarPathOf(outFile);
282711
- if (!existsSync25(sc)) {
283652
+ if (!existsSync26(sc)) {
282712
283653
  mkdirSync7(dirname27(sc), { recursive: true });
282713
283654
  writeFileSync10(sc, draft.draftSource, "utf-8");
282714
283655
  sidecarDraft = sc;
@@ -282744,11 +283685,11 @@ function runTscBaseline(root, files) {
282744
283685
  }
282745
283686
  async function migrateVerify(paths, opts = {}) {
282746
283687
  const { checkSource: checkSource2, formatCheckReport: formatCheckReport2, pTrue: pTrue3 } = await Promise.resolve().then(() => (init_src(), src_exports));
282747
- const { defaultLoadModule: defaultLoadModule3, findProjectConfig: findProjectConfig3, interfaceConfig: interfaceConfig3, checkConfig: checkConfig2 } = await Promise.resolve().then(() => (init_src4(), src_exports4));
283688
+ const { collectSkipReturns: collectSkipReturns2, defaultLoadModule: defaultLoadModule3, findProjectConfig: findProjectConfig3, interfaceConfig: interfaceConfig3, checkConfig: checkConfig2 } = await Promise.resolve().then(() => (init_src4(), src_exports4));
282748
283689
  const files = [];
282749
283690
  for (const p of paths) {
282750
283691
  const abs2 = resolve19(p);
282751
- if (!existsSync25(abs2)) {
283692
+ if (!existsSync26(abs2)) {
282752
283693
  throw new Error(`not found: ${p}`);
282753
283694
  }
282754
283695
  if (statSync15(abs2).isDirectory()) {
@@ -282774,7 +283715,9 @@ async function migrateVerify(paths, opts = {}) {
282774
283715
  ...autoBind === false ? { autoBind: false } : {},
282775
283716
  ...proj?.projectDir ? { projectDir: proj.projectDir } : {},
282776
283717
  entryThrows: cCfg.entryThrows,
282777
- ...cCfg.ignoreThrows && cCfg.ignoreThrows.length > 0 ? { ignoreThrows: cCfg.ignoreThrows } : {}
283718
+ ...cCfg.ignoreThrows && cCfg.ignoreThrows.length > 0 ? { ignoreThrows: cCfg.ignoreThrows } : {},
283719
+ // 与 nudo check 同源:@nudo:skip 的函数不评估 body(verify 的 ok 必须等于 check)
283720
+ skips: collectSkipReturns2(source)
282778
283721
  });
282779
283722
  const summary = `${report.summary.errors} error \xB7 ${report.summary.warnings} warning \xB7 ${report.summary.functions} fn`;
282780
283723
  const row = {
@@ -282843,7 +283786,7 @@ function migrateRetire(rootDir, opts = {}) {
282843
283786
  mkdirSync7(markerDir, { recursive: true });
282844
283787
  commitWriteSet(root, ops);
282845
283788
  const retiring = join19(markerDir, "migrate-retiring.json");
282846
- if (existsSync25(retiring)) rmSync3(retiring, { force: true });
283789
+ if (existsSync26(retiring)) rmSync3(retiring, { force: true });
282847
283790
  }
282848
283791
  return {
282849
283792
  root: relative12(process.cwd(), root) || ".",
@@ -283026,6 +283969,21 @@ function usageError(message, fix) {
283026
283969
  console.error(message);
283027
283970
  console.error(`fix: ${fix}`);
283028
283971
  }
283972
+ function displayPathOf(p) {
283973
+ const rel = relative7(process.cwd(), p);
283974
+ return rel === "" || rel.startsWith("..") ? p : rel;
283975
+ }
283976
+ function variadicSwallowError(command, flags) {
283977
+ const list = flags.join(", ");
283978
+ const first = flags[0] ?? "--from";
283979
+ console.error(
283980
+ `error: \`nudo ${command}\` received no paths \u2014 variadic flag(s) ${list} consumed the following arguments`
283981
+ );
283982
+ console.error(
283983
+ `fix: separate variadic flags from paths with \`--\` (e.g. \`nudo ${command} ${first} a.js -- b.js\`) or list paths before the flags`
283984
+ );
283985
+ process.exitCode = 1;
283986
+ }
283029
283987
  function reportPathErrors(errors) {
283030
283988
  for (const e of errors) usageError(e.message, e.suggestion);
283031
283989
  if (errors.length > 0) process.exitCode = 1;
@@ -283213,7 +284171,8 @@ function startWatch(paths, runOne, label) {
283213
284171
  console.log(`[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] Watching for changes...`);
283214
284172
  graph = buildModuleGraph(getFiles());
283215
284173
  };
283216
- runAll().catch(() => {
284174
+ runAll().catch((err) => {
284175
+ console.error(`nudo ${label} failed to start: ${err.message}`);
283217
284176
  });
283218
284177
  let debounceTimer = null;
283219
284178
  const watchTarget = isDir ? primary : dirname21(primary);
@@ -283432,6 +284391,7 @@ async function runCheck(file, opts = {}) {
283432
284391
  let cachedJson;
283433
284392
  let algebraReport;
283434
284393
  let mockFromErrors = [];
284394
+ let injectionSetupFailed = false;
283435
284395
  if (cached2) {
283436
284396
  cachedJson = cached2;
283437
284397
  algebraReport = reportFromCachedJson(cached2);
@@ -283478,7 +284438,12 @@ async function runCheck(file, opts = {}) {
283478
284438
  ...reps.targets.length > 0 ? { replacements: reps.values, replacementTargets: reps.targets } : {},
283479
284439
  ...reps.asTargets.length > 0 ? { asOverrides: reps.asValues, asOverrideTargets: reps.asTargets } : {}
283480
284440
  };
283481
- } catch {
284441
+ } catch (err) {
284442
+ console.error(
284443
+ `error: eval injection setup failed: ${err.message}`
284444
+ );
284445
+ injectionSetupFailed = true;
284446
+ if (opts.gateFlags) opts.gateFlags.injectionSetupFailed = true;
283482
284447
  }
283483
284448
  algebraReport = checkSource2(filePath, source, pTrue3, {
283484
284449
  loadModule,
@@ -283596,31 +284561,32 @@ async function runCheck(file, opts = {}) {
283596
284561
  })) {
283597
284562
  printDocsLinks(algebraReport.issues);
283598
284563
  }
283599
- if (useDisk && cacheKey && !cached2 && !opts.abs) {
284564
+ if (useDisk && cacheKey && !cached2 && !opts.abs && !injectionSetupFailed) {
283600
284565
  try {
283601
284566
  disk.set(cacheKey, serializeCheckJson2(algebraReport));
283602
284567
  } catch {
283603
284568
  }
283604
284569
  }
283605
284570
  if (opts.json && !opts.jsonCollect) {
283606
- process.exitCode = checkJson.ok ? 0 : 1;
283607
- } else if (!opts.jsonCollect && !algebraReport.ok) {
284571
+ process.exitCode = checkJson.ok && !injectionSetupFailed ? 0 : 1;
284572
+ } else if (!opts.jsonCollect && (!algebraReport.ok || injectionSetupFailed)) {
283608
284573
  process.exitCode = 1;
283609
284574
  }
283610
284575
  }
283611
284576
  async function runCheckFix(opts) {
283612
284577
  const core = await Promise.resolve().then(() => (init_src(), src_exports));
283613
284578
  const { checkSource: checkSource2, pTrue: pTrue3, sidecarPathOf: sidecarPathOf2, actionsForIssue: actionsForIssue2 } = core;
283614
- const { collectSkipReturns: collectSkipReturns2, defaultLoadModule: defaultLoadModule3 } = await Promise.resolve().then(() => (init_src4(), src_exports4));
284579
+ const { collectSkipReturns: collectSkipReturns2, defaultLoadModule: defaultLoadModule3, findProjectConfig: findProjectConfig3, checkConfig: checkConfig2 } = await Promise.resolve().then(() => (init_src4(), src_exports4));
283615
284580
  const {
283616
284581
  materializeAction: materializeAction2,
283617
284582
  applyTextEdits: applyTextEdits2,
283618
284583
  unifiedDiff: unifiedDiff2
283619
284584
  } = await Promise.resolve().then(() => (init_emit2(), emit_exports));
283620
- const { writeFileSync: writeFileSync11, readFileSync: readFileSync29, existsSync: existsSync26 } = await import("fs");
284585
+ const { writeFileSync: writeFileSync11, readFileSync: readFileSync29, existsSync: existsSync27 } = await import("fs");
283621
284586
  const only = opts.only && opts.only.length > 0 ? new Set(opts.only) : void 0;
283622
284587
  let planned = 0;
283623
284588
  let written = 0;
284589
+ let residualErrors = 0;
283624
284590
  for (const file of opts.targets) {
283625
284591
  let source;
283626
284592
  try {
@@ -283628,17 +284594,26 @@ async function runCheckFix(opts) {
283628
284594
  } catch {
283629
284595
  continue;
283630
284596
  }
284597
+ const proj = findProjectConfig3(dirname24(file));
284598
+ const cCfg = checkConfig2(proj?.config);
284599
+ const gate = checkGateFromConfig(proj?.config);
284600
+ const entryThrows = resolveEntryThrows(
284601
+ { entryThrows: opts.entryThrows, profile: opts.profile },
284602
+ gate,
284603
+ cCfg.entryThrows
284604
+ );
284605
+ const ignoreThrows = mergeIgnoreThrows(opts.ignoreThrows, cCfg.ignoreThrows);
283631
284606
  const report = checkSource2(file, source, pTrue3, {
283632
284607
  loadModule: defaultLoadModule3,
283633
284608
  fromFile: file,
283634
- ...opts.ignoreThrows ? { ignoreThrows: opts.ignoreThrows } : {},
283635
- ...opts.entryThrows ? { entryThrows: opts.entryThrows } : {},
284609
+ ...ignoreThrows.length > 0 ? { ignoreThrows } : {},
284610
+ entryThrows,
283636
284611
  skips: collectSkipReturns2(source)
283637
284612
  });
283638
284613
  const sidecar = sidecarPathOf2(file);
283639
284614
  let sidecarText;
283640
284615
  try {
283641
- sidecarText = existsSync26(sidecar) ? readFileSync29(sidecar, "utf-8") : void 0;
284616
+ sidecarText = existsSync27(sidecar) ? readFileSync29(sidecar, "utf-8") : void 0;
283642
284617
  } catch {
283643
284618
  sidecarText = void 0;
283644
284619
  }
@@ -283699,6 +284674,16 @@ async function runCheckFix(opts) {
283699
284674
  }
283700
284675
  }
283701
284676
  }
284677
+ if (report.issues.some((i) => i.severity === "error")) {
284678
+ const post = checkSource2(file, source, pTrue3, {
284679
+ loadModule: defaultLoadModule3,
284680
+ fromFile: file,
284681
+ ...ignoreThrows.length > 0 ? { ignoreThrows } : {},
284682
+ entryThrows,
284683
+ skips: collectSkipReturns2(source)
284684
+ });
284685
+ residualErrors += post.issues.filter((i) => i.severity === "error").length;
284686
+ }
283702
284687
  }
283703
284688
  const mode = opts.write ? "written" : "planned (dry-run)";
283704
284689
  console.log(
@@ -283710,9 +284695,13 @@ check --fix: ${planned} edit(s) ${mode}${opts.write ? `, ${written} applied` : "
283710
284695
  "hint: only [fix]/[adjust] are auto-applied; [silence]/[review] need a human (or LSP quickfix)"
283711
284696
  );
283712
284697
  }
284698
+ if (residualErrors > 0) {
284699
+ console.log(`check --fix: ${residualErrors} residual error(s) remain \u2014 gate stays red`);
284700
+ }
284701
+ return { planned, written, residualErrors };
283713
284702
  }
283714
284703
  function registerCheckCommand(program3) {
283715
- program3.command("check").description("Gate contracts + entry throws; print signatures (CI). Day 0 observation lives here.").argument("<paths...>", "File(s) or directory(s) to check").option("--watch, -w", "Watch files and re-run check on change").option("--json", "Emit stable CheckJson (1 file) or CheckJsonMulti envelope (N files) for CI / Agent").option("--gha", "GitHub Actions inline annotations (::error/::warning). Auto when GITHUB_ACTIONS=true").option("--gitlab", "GitLab Code Quality JSON array (write as gl-code-quality-report.json)").option("--verbose", "Expand Abs signatures (term/pred/conf detail)").option("--abs", "Algebra face: term/pred/conf per function").option("--fn <name>", "With --abs: only this function").option("--assume <pred...>", "With --abs: assume constraints, e.g. x>0 y>=1").option("--generalize", "With --abs: polymorphic signatures via symbolic execution").option(
284704
+ program3.command("check").description("Gate contracts + entry throws; print signatures (CI). Day 0 observation lives here.").argument("[paths...]", "File(s) or directory(s) to check").option("--watch, -w", "Watch files and re-run check on change").option("--json", "Emit stable CheckJson (1 file) or CheckJsonMulti envelope (N files) for CI / Agent").option("--gha", "GitHub Actions inline annotations (::error/::warning). Auto when GITHUB_ACTIONS=true").option("--gitlab", "GitLab Code Quality JSON array (write as gl-code-quality-report.json)").option("--verbose", "Expand Abs signatures (term/pred/conf detail)").option("--abs", "Algebra face: term/pred/conf per function").option("--fn <name>", "With --abs: only this function").option("--assume <pred...>", "With --abs: assume constraints, e.g. x>0 y>=1").option("--generalize", "With --abs: polymorphic signatures via symbolic execution").option(
283716
284705
  "--from <paths...>",
283717
284706
  "Usage-site files: inject call records for cross-file domain evidence"
283718
284707
  ).option(
@@ -283729,6 +284718,28 @@ function registerCheckCommand(program3) {
283729
284718
  "AI3: assume `name:type` bindings (e.g. raw:string) and report --target"
283730
284719
  ).option("--target <name>", "With --what-if: binding name whose inferred type to print").option("--fix", "Materialize one fix/adjust edit per issue (default --dry-run: print unified diff)").option("--only <codes...>", "With --fix: only these diagnostic codes (e.g. nudo:entry-may-throw)").option("--write", "With --fix: apply edits to disk (default dry-run)").option("--dry-run", "With --fix: print diffs only (default; kept for explicitness)").action(
283731
284720
  async (paths, opts) => {
284721
+ if (paths.length === 0) {
284722
+ const swallowed = [
284723
+ ...opts.from?.length ? ["--from"] : [],
284724
+ ...opts.assume?.length ? ["--assume"] : [],
284725
+ ...opts.whatIf?.length ? ["--what-if"] : [],
284726
+ ...opts.only?.length ? ["--only"] : []
284727
+ ];
284728
+ if (swallowed.length > 0) {
284729
+ variadicSwallowError("check", swallowed);
284730
+ return;
284731
+ }
284732
+ console.error("Usage error: `nudo check` needs at least one path.");
284733
+ process.exitCode = 1;
284734
+ return;
284735
+ }
284736
+ if (opts.fix && (opts.json || opts.abs || opts.gha || opts.gitlab || opts.watch || opts.verbose || opts.from && opts.from.length > 0 || opts.whatIf && opts.whatIf.length > 0)) {
284737
+ console.error(
284738
+ "error: --fix cannot be combined with --json/--abs/--gha/--gitlab/--watch/--verbose/--from/--what-if (materialization face; the CI gate is plain `nudo check`)"
284739
+ );
284740
+ process.exitCode = 1;
284741
+ return;
284742
+ }
283732
284743
  if (opts.whatIf && opts.whatIf.length > 0) {
283733
284744
  const targets2 = [];
283734
284745
  for (const p of paths) targets2.push(...resolveTargets(p));
@@ -283786,7 +284797,7 @@ function registerCheckCommand(program3) {
283786
284797
  const entryThrows = isEntryThrowsMode(opts.entryThrows) ? opts.entryThrows : void 0;
283787
284798
  const profile = isGateProfile(opts.profile) ? opts.profile : void 0;
283788
284799
  if (opts.fix) {
283789
- await runCheckFix({
284800
+ const fix = await runCheckFix({
283790
284801
  targets,
283791
284802
  only: opts.only,
283792
284803
  write: opts.write === true,
@@ -283794,6 +284805,9 @@ function registerCheckCommand(program3) {
283794
284805
  entryThrows,
283795
284806
  profile
283796
284807
  });
284808
+ if (fix.residualErrors > 0) {
284809
+ process.exitCode = 1;
284810
+ }
283797
284811
  return;
283798
284812
  }
283799
284813
  const shared = {
@@ -283817,13 +284831,14 @@ function registerCheckCommand(program3) {
283817
284831
  const wantMulti = targets.length > 1 || allPathErrors.length > 0;
283818
284832
  if (wantMulti) {
283819
284833
  const collected = [];
284834
+ const gateFlags = {};
283820
284835
  for (const t of targets) {
283821
- await runCheck(t, { ...shared, json: true, jsonCollect: collected });
284836
+ await runCheck(t, { ...shared, json: true, jsonCollect: collected, gateFlags });
283822
284837
  }
283823
284838
  const { serializeCheckJsonMulti: multi } = await Promise.resolve().then(() => (init_src(), src_exports));
283824
284839
  const envelope = attachPathErrors(multi(collected), allPathErrors);
283825
284840
  console.log(JSON.stringify(envelope, null, 2));
283826
- process.exitCode = envelope.ok ? 0 : 1;
284841
+ process.exitCode = envelope.ok && !gateFlags.injectionSetupFailed ? 0 : 1;
283827
284842
  return;
283828
284843
  }
283829
284844
  if (targets.length === 1) {
@@ -284046,13 +285061,13 @@ async function runTest(file, opts = {}) {
284046
285061
  if (report.failed > 0 || pathErrors.length > 0) process.exitCode = 1;
284047
285062
  else process.exitCode = 0;
284048
285063
  } else if (opts.abs) {
284049
- const report = buildTestReport(filePath, result);
285064
+ const report = buildTestReport(displayPathOf(filePath), result);
284050
285065
  await runAbsView(filePath, {});
284051
285066
  console.log("");
284052
285067
  console.log(formatTestReport(report));
284053
285068
  if (report.failed > 0) process.exitCode = 1;
284054
285069
  } else {
284055
- const report = buildTestReport(filePath, result);
285070
+ const report = buildTestReport(displayPathOf(filePath), result);
284056
285071
  console.log(formatTestReport(report));
284057
285072
  if (report.failed > 0) process.exitCode = 1;
284058
285073
  }
@@ -284091,8 +285106,17 @@ freeze \u2192 ${relPath} (${directiveCount} directive(s) across ${emitOut.writte
284091
285106
  }
284092
285107
  }
284093
285108
  function registerTestCommand(program3) {
284094
- program3.command("test").description("Report every inferred case (call@/entry@ + debug witnesses); assert declared expectations").argument("<paths...>", "File(s) or directory(s)").option("--watch, -w", "Watch files and re-run test on change").option("--from <paths...>", "Usage-site files whose calls become synthesized cases").option("--freeze [mode]", "Solidify call-site witnesses as @nudo:case (mode: update | add; add is the default when the value is omitted)").option("--dry-run", "With --freeze: print a unified diff instead of writing").option("--exit-on-diff", "With --freeze --dry-run: exit 1 when the diff is non-empty").option("--json", "Output case facts as JSON (single file)").option("--abs", "Algebra face via check --abs").action(
285109
+ program3.command("test").description("Report every inferred case (call@/entry@ + debug witnesses); assert declared expectations").argument("[paths...]", "File(s) or directory(s)").option("--watch, -w", "Watch files and re-run test on change").option("--from <paths...>", "Usage-site files whose calls become synthesized cases").option("--freeze [mode]", "Solidify call-site witnesses as @nudo:case (mode: update | add; add is the default when the value is omitted)").option("--dry-run", "With --freeze: print a unified diff instead of writing").option("--exit-on-diff", "With --freeze --dry-run: exit 1 when the diff is non-empty").option("--json", "Output case facts as JSON (single file)").option("--abs", "Algebra face via check --abs").action(
284095
285110
  async (paths, opts) => {
285111
+ if (paths.length === 0) {
285112
+ if (opts.from?.length) {
285113
+ variadicSwallowError("test", ["--from"]);
285114
+ return;
285115
+ }
285116
+ console.error("Usage error: `nudo test` needs at least one path.");
285117
+ process.exitCode = 1;
285118
+ return;
285119
+ }
284096
285120
  const targets = [];
284097
285121
  const pathErrors = [];
284098
285122
  for (const p of paths) {
@@ -284481,6 +285505,10 @@ function registerContractCommand(program3) {
284481
285505
  return;
284482
285506
  }
284483
285507
  if (paths.length === 0) {
285508
+ if (opts.from?.length) {
285509
+ variadicSwallowError("contract", ["--from"]);
285510
+ return;
285511
+ }
284484
285512
  console.error(
284485
285513
  "Usage error: `nudo contract` needs at least one path. " + (opts.emit ? "Writing filters: --fn <names> / --all (default: only refresh existing @generated segments)." : opts.draft ? "Draft mode: pass a file or directory." : "Print-only: pass a file or directory.")
284486
285514
  );
@@ -284572,11 +285600,11 @@ function registerContractCommand(program3) {
284572
285600
  }
284573
285601
 
284574
285602
  // src/commands/export.ts
285603
+ import { readFileSync as readFileSync26, writeFileSync as writeFileSync9, mkdirSync as mkdirSync6 } from "fs";
285604
+ import { resolve as resolve17, relative as relative10, join as join18, basename as basename8 } from "path";
284575
285605
  init_src();
284576
285606
  init_emit2();
284577
285607
  init_src4();
284578
- import { readFileSync as readFileSync26, writeFileSync as writeFileSync9, mkdirSync as mkdirSync6 } from "fs";
284579
- import { resolve as resolve17, relative as relative10, join as join18, basename as basename8 } from "path";
284580
285608
 
284581
285609
  // src/export-format.ts
284582
285610
  var EXPORT_FORMATS = ["schema", "standard", "guard", "dts", "all"];
@@ -284704,34 +285732,50 @@ ${mod2.source}`);
284704
285732
  const schemaModule = Object.keys(schemaExports).length > 0 ? absToZodSchemaModule(schemaExports) : void 0;
284705
285733
  if (output) {
284706
285734
  const outDir = resolve17(output);
284707
- mkdirSync6(outDir, { recursive: true });
285735
+ const ioError = (op) => ({
285736
+ path: outDir,
285737
+ code: "nudo:path-io",
285738
+ message: `export --out ${op} failed: ${displayPathOf(outDir)}`,
285739
+ suggestion: `check --out <dir> is writable and the path is a directory, then re-run nudo export`
285740
+ });
285741
+ try {
285742
+ mkdirSync6(outDir, { recursive: true });
285743
+ } catch {
285744
+ reportPathErrors([ioError("mkdir")]);
285745
+ return;
285746
+ }
284708
285747
  const written = [];
284709
- if (schemaModule) {
284710
- const p = join18(outDir, schemaFileName(stem, effectiveDialect));
284711
- writeFileSync9(p, schemaModule.source, "utf-8");
284712
- written.push(p);
284713
- }
284714
- if (standardChunks.length > 0) {
284715
- for (const chunk of standardChunks) {
284716
- const m = chunk.match(/\/\/ === (\S+) Standard Schema ===/);
284717
- const fnStem = m?.[1] ?? stem;
284718
- const p = join18(outDir, `${fnStem}.nudo.standard.ts`);
284719
- const header = `// @generated by nudo export --format standard \u2014 Standard Schema v1 (vendor: nudo)
285748
+ try {
285749
+ if (schemaModule) {
285750
+ const p = join18(outDir, schemaFileName(stem, effectiveDialect));
285751
+ writeFileSync9(p, schemaModule.source, "utf-8");
285752
+ written.push(p);
285753
+ }
285754
+ if (standardChunks.length > 0) {
285755
+ for (const chunk of standardChunks) {
285756
+ const m = chunk.match(/\/\/ === (\S+) Standard Schema ===/);
285757
+ const fnStem = m?.[1] ?? stem;
285758
+ const p = join18(outDir, `${fnStem}.nudo.standard.ts`);
285759
+ const header = `// @generated by nudo export --format standard \u2014 Standard Schema v1 (vendor: nudo)
284720
285760
  `;
284721
- const body = chunk.startsWith("\n") ? chunk.slice(1) : chunk;
284722
- writeFileSync9(p, body.includes("@generated") ? body + "\n" : header + body + "\n", "utf-8");
285761
+ const body = chunk.startsWith("\n") ? chunk.slice(1) : chunk;
285762
+ writeFileSync9(p, body.includes("@generated") ? body + "\n" : header + body + "\n", "utf-8");
285763
+ written.push(p);
285764
+ }
285765
+ }
285766
+ if (guardChunks.length > 0) {
285767
+ const p = join18(outDir, `${stem}.nudo.guard.ts`);
285768
+ writeFileSync9(p, guardChunks.join("\n") + "\n", "utf-8");
284723
285769
  written.push(p);
284724
285770
  }
284725
- }
284726
- if (guardChunks.length > 0) {
284727
- const p = join18(outDir, `${stem}.nudo.guard.ts`);
284728
- writeFileSync9(p, guardChunks.join("\n") + "\n", "utf-8");
284729
- written.push(p);
284730
- }
284731
- if (dtsChunks.length > 0) {
284732
- const p = join18(outDir, `${stem}.d.ts`);
284733
- writeFileSync9(p, dtsChunks.join("\n") + "\n", "utf-8");
284734
- written.push(p);
285771
+ if (dtsChunks.length > 0) {
285772
+ const p = join18(outDir, `${stem}.d.ts`);
285773
+ writeFileSync9(p, dtsChunks.join("\n") + "\n", "utf-8");
285774
+ written.push(p);
285775
+ }
285776
+ } catch {
285777
+ reportPathErrors([ioError("write")]);
285778
+ return;
284735
285779
  }
284736
285780
  for (const p of written) {
284737
285781
  console.log(`wrote ${relative10(process.cwd(), p)}`);
@@ -284776,21 +285820,30 @@ function registerExportCommand(program3) {
284776
285820
  process.exitCode = 1;
284777
285821
  return;
284778
285822
  }
284779
- await runExport(file, format, options.out, dialect);
285823
+ const { targets, errors } = resolveTargetsCollect(file);
285824
+ if (errors.length > 0) {
285825
+ reportPathErrors(errors);
285826
+ return;
285827
+ }
285828
+ if (targets.length !== 1) {
285829
+ console.error(
285830
+ `error: export takes exactly one analysis file (got ${targets.length} from ${file})`
285831
+ );
285832
+ console.error("fix: pass the single .js/.mjs/.ts file to project");
285833
+ process.exitCode = 1;
285834
+ return;
285835
+ }
285836
+ await runExport(targets[0], format, options.out, dialect);
284780
285837
  }
284781
285838
  );
284782
285839
  }
284783
285840
 
284784
285841
  // src/commands/health.ts
284785
285842
  init_src4();
284786
- import { readFileSync as readFileSync27, existsSync as existsSync24, statSync as statSync14 } from "fs";
284787
- import { resolve as resolve18, dirname as dirname26, relative as relative11 } from "path";
284788
- var displayPath = (p) => {
284789
- const rel = relative11(process.cwd(), p);
284790
- return rel === "" || rel.startsWith("..") ? p : rel;
284791
- };
285843
+ import { readFileSync as readFileSync27, existsSync as existsSync25, statSync as statSync14 } from "fs";
285844
+ import { resolve as resolve18, dirname as dirname26 } from "path";
284792
285845
  async function healthFile(filePath, records) {
284793
- const report = { file: displayPath(filePath), functions: 0, entryOnly: 0, uncovered: [] };
285846
+ const report = { file: displayPathOf(filePath), functions: 0, entryOnly: 0, uncovered: [] };
284794
285847
  let source;
284795
285848
  try {
284796
285849
  source = readFileSync27(filePath, "utf-8");
@@ -284859,13 +285912,16 @@ async function countInterfaceDrift(filePath) {
284859
285912
  }
284860
285913
  if (!/@generated/.test(scSrc)) return { count: 0 };
284861
285914
  try {
284862
- const { findProjectConfig: findProjectConfig3, interfaceConfig: interfaceConfig3 } = await Promise.resolve().then(() => (init_src4(), src_exports4));
285915
+ const { collectSkipReturns: collectSkipReturns2, findProjectConfig: findProjectConfig3, interfaceConfig: interfaceConfig3 } = await Promise.resolve().then(() => (init_src4(), src_exports4));
284863
285916
  const proj = findProjectConfig3(dirname26(abs2));
284864
285917
  const autoBind = interfaceConfig3(proj?.config).autoBind;
284865
- const r = checkSource2(abs2, rf(abs2, "utf-8"), pTrue3, {
285918
+ const source = rf(abs2, "utf-8");
285919
+ const r = checkSource2(abs2, source, pTrue3, {
284866
285920
  loadModule: defaultLoadModule3,
284867
285921
  fromFile: abs2,
284868
- ...autoBind === false ? { autoBind: false } : {}
285922
+ ...autoBind === false ? { autoBind: false } : {},
285923
+ // 与 nudo check 同源:@nudo:skip 的函数不评估 body,不产出 drift 候选
285924
+ skips: collectSkipReturns2(source)
284869
285925
  });
284870
285926
  return { count: r.issues.filter((i) => i.code === "nudo:interface-drift").length };
284871
285927
  } catch (e) {
@@ -284874,13 +285930,14 @@ async function countInterfaceDrift(filePath) {
284874
285930
  }
284875
285931
  async function runHealth(paths, opts) {
284876
285932
  const targetPaths = paths.length > 0 ? paths : ["."];
284877
- const externalRecords = opts.from?.length ? collectExternalRecords(opts.from) : void 0;
285933
+ const fromErrors = [];
285934
+ const externalRecords = opts.from?.length ? collectExternalRecords(opts.from, fromErrors) : void 0;
284878
285935
  const files = [];
284879
285936
  const reports = [];
284880
285937
  for (const p of targetPaths) {
284881
285938
  const abs2 = resolve18(p);
284882
- if (!existsSync24(abs2)) {
284883
- reports.push({ file: displayPath(abs2), functions: 0, entryOnly: 0, uncovered: [], error: `File not found: ${abs2}` });
285939
+ if (!existsSync25(abs2)) {
285940
+ reports.push({ file: displayPathOf(abs2), functions: 0, entryOnly: 0, uncovered: [], error: `File not found: ${displayPathOf(abs2)}` });
284884
285941
  continue;
284885
285942
  }
284886
285943
  files.push(...statSync14(abs2).isDirectory() ? collectNudoFiles(abs2) : [abs2]);
@@ -284893,11 +285950,21 @@ async function runHealth(paths, opts) {
284893
285950
  const errorCount = reports.filter((r) => r.error).length;
284894
285951
  const uncoveredTotal = reports.reduce((n, r) => n + r.uncovered.length, 0);
284895
285952
  const failed = driftCount > 0 || ifaceDriftCount > 0 || errorCount > 0;
285953
+ const ok2 = !failed && fromErrors.length === 0;
285954
+ process.exitCode = ok2 ? 0 : 1;
284896
285955
  if (opts.json) {
284897
285956
  console.log(
284898
285957
  JSON.stringify(
284899
285958
  {
284900
- ok: !failed,
285959
+ ok: ok2,
285960
+ ...fromErrors.length > 0 ? {
285961
+ pathErrors: fromErrors.map((e) => ({
285962
+ path: e.path,
285963
+ code: e.code,
285964
+ message: e.message,
285965
+ suggestion: e.suggestion
285966
+ }))
285967
+ } : {},
284901
285968
  files: reports.map((r) => ({
284902
285969
  file: r.file,
284903
285970
  functions: r.functions,
@@ -284912,7 +285979,7 @@ async function runHealth(paths, opts) {
284912
285979
  files: reports.length,
284913
285980
  drift: driftCount,
284914
285981
  interfaceDrift: ifaceDriftCount,
284915
- errors: errorCount,
285982
+ errors: errorCount + fromErrors.length,
284916
285983
  uncovered: uncoveredTotal,
284917
285984
  internal_fallbacks: reports.reduce((n, r) => n + (r.internalFallbacks ?? 0), 0)
284918
285985
  }
@@ -284922,8 +285989,16 @@ async function runHealth(paths, opts) {
284922
285989
  )
284923
285990
  );
284924
285991
  } else {
285992
+ for (const e of fromErrors) {
285993
+ console.error(e.message);
285994
+ console.error(`fix: ${e.suggestion}`);
285995
+ }
284925
285996
  if (reports.length === 0) {
284926
- console.log("No files to check.");
285997
+ if (fromErrors.length > 0) {
285998
+ console.log("Result: FAIL (path errors found)");
285999
+ } else {
286000
+ console.log("No files to check.");
286001
+ }
284927
286002
  return;
284928
286003
  }
284929
286004
  for (const r of reports) {
@@ -284962,12 +286037,27 @@ async function runHealth(paths, opts) {
284962
286037
  `
284963
286038
  Summary: ${reports.length} file(s) \xB7 ${driftCount} case drift \xB7 ${ifaceDriftCount} contract drift \xB7 ${errorCount} error(s) \xB7 ${uncoveredTotal} uncovered function(s)` + (internalTotal > 0 ? ` \xB7 ${internalTotal} internal_fallback(s)` : "")
284964
286039
  );
284965
- console.log(failed ? "Result: FAIL (drift or errors found)" : "Result: OK (uncovered function(s) are informational only)");
286040
+ console.log(
286041
+ ok2 ? "Result: OK (uncovered function(s) are informational only)" : fromErrors.length > 0 && !failed ? "Result: FAIL (path errors found)" : "Result: FAIL (drift or errors found)"
286042
+ );
284966
286043
  }
284967
- if (failed) process.exitCode = 1;
284968
286044
  }
284969
286045
  function registerHealthCommand(program3) {
284970
- program3.command("health").description("Project health & drift: uncovered functions, witness drift, contract drift, analysis errors").argument("[paths...]", "File(s) or directory(s) (default: current directory)").option("--watch, -w", "Watch and re-run health on change").option("--from <paths...>", "Usage-site files for freeze-drift detection").option("--json", "Output as JSON").action(async (paths, opts) => {
286046
+ program3.command("health").description("Project health & drift: uncovered functions, witness drift, contract drift, analysis errors").argument("[paths...]", "File(s) or directory(s) (default: current directory; with --from, pass paths explicitly \u2014 e.g. `nudo health . --from usage.js`)").option("--watch, -w", "Watch and re-run health on change").option("--from <paths...>", "Usage-site files for freeze-drift detection").option("--json", "Output as JSON").action(async (paths, opts) => {
286047
+ if (opts.from?.length) {
286048
+ if (opts.from.length > 1) {
286049
+ variadicSwallowError("health", ["--from"]);
286050
+ return;
286051
+ }
286052
+ if (paths.length === 0) {
286053
+ console.error(
286054
+ "error: `nudo health` --from requires explicit paths when used without positional paths (previously defaulted to cwd scan)"
286055
+ );
286056
+ console.error("fix: use: `nudo health . --from usage.js`");
286057
+ process.exitCode = 1;
286058
+ return;
286059
+ }
286060
+ }
284971
286061
  const fromPaths = opts.from;
284972
286062
  const runOneDir = async () => {
284973
286063
  await runHealth(paths, { ...fromPaths ? { from: fromPaths } : {}, json: opts.json });
@@ -285099,7 +286189,7 @@ function registerMigrateCommand(program3) {
285099
286189
 
285100
286190
  // src/index.ts
285101
286191
  var program2 = new Command();
285102
- var argv = process.argv.filter((a, i) => !(i >= 2 && a === "--"));
286192
+ var argv = process.argv[2] === "--" ? [process.argv[0], process.argv[1], ...process.argv.slice(3)] : process.argv;
285103
286193
  program2.name("nudo").description("JavaScript types, computed \u2014 check / test / contract / export / health / migrate").version(formatVersionOutput()).addHelpText(
285104
286194
  "after",
285105
286195
  `