@nudojs/core 1.13.0 → 1.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,18 +2,21 @@ import "./chunk-US532PZA.js";
2
2
  import {
3
3
  INTERFACE_SOURCE_ABBR,
4
4
  NudoSidecarError,
5
+ actionsForIssue,
5
6
  buildArgsFromAssume,
6
7
  canSkipLiteralCallScan,
7
8
  collectAbsFreeVars,
8
9
  effectiveInterface,
9
10
  evictGeneralizeMemoForPaths,
10
11
  execNudoModule,
12
+ extractAllLoadSpecs,
11
13
  extractAssumeSegs,
12
14
  extractBalancedParens,
13
15
  extractDeclaredThrows,
14
16
  extractFileEnvNames,
15
17
  extractFn,
16
18
  extractFnBudget,
19
+ extractInvariantSegs,
17
20
  extractMockModuleRecords,
18
21
  extractNudoImportRecords,
19
22
  extractNudoImports,
@@ -22,8 +25,11 @@ import {
22
25
  findFnDirectiveScope,
23
26
  fnDirectiveCommentLines,
24
27
  fnTopLevelLocalNames,
28
+ formatCheckReport,
25
29
  formatConstraint,
26
30
  formatEffectiveInterfaceDisplay,
31
+ formatGithubAnnotations,
32
+ formatGitlabCodeQuality,
27
33
  formatInterfaceTierLine,
28
34
  generalizeAll,
29
35
  generalizeFromAst,
@@ -56,6 +62,8 @@ import {
56
62
  scanContractSegments,
57
63
  scanLiteralCalls,
58
64
  scanThrowsDecl,
65
+ serializeCheckJson,
66
+ serializeCheckJsonMulti,
59
67
  setInterfaceDiagCollector,
60
68
  setRefineDiagCollector,
61
69
  sidecarClosureFingerprint,
@@ -66,7 +74,7 @@ import {
66
74
  takeInterfaceDiagsSince,
67
75
  takeRefineDiags,
68
76
  takeRefineDiagsSince
69
- } from "./chunk-V2XC3V72.js";
77
+ } from "./chunk-4GZPH6YL.js";
70
78
  import {
71
79
  $add,
72
80
  $arguments,
@@ -525,8 +533,9 @@ import {
525
533
  v,
526
534
  withAssumePremises,
527
535
  withExecPhi,
536
+ withInvariantSpecs,
528
537
  withVar
529
- } from "./chunk-A2PLG2QV.js";
538
+ } from "./chunk-JMOQEJTD.js";
530
539
 
531
540
  // src/environment.ts
532
541
  function createEnvironment(parent, bindings = /* @__PURE__ */ new Map()) {
@@ -759,6 +768,9 @@ function formatDiagnostics(diags) {
759
768
  }).join("\n");
760
769
  }
761
770
 
771
+ // src/algebra/check.ts
772
+ import { isBuiltin } from "module";
773
+
762
774
  // src/algebra/postcondition.ts
763
775
  function proved() {
764
776
  return { status: "proved" };
@@ -2413,12 +2425,63 @@ function assumePremisesPhiOf(name, source, file, issues) {
2413
2425
  if (preds.length === 0) return void 0;
2414
2426
  return { phi: and(...preds), display };
2415
2427
  }
2428
+ function invariantSpecsOf(name, source, file, issues) {
2429
+ const segs = extractInvariantSegs(source, name);
2430
+ if (segs.length === 0) return void 0;
2431
+ const meta = extractFn(source, name, file);
2432
+ const bindable = /* @__PURE__ */ new Set([
2433
+ ...contractParamNameSet(meta?.formals ?? []),
2434
+ ...meta?.params ?? [],
2435
+ ...fnTopLevelLocalNames(meta?.body)
2436
+ ]);
2437
+ const surface = [...bindable].join(", ");
2438
+ const specs = [];
2439
+ const display = [];
2440
+ for (const seg of segs) {
2441
+ if (seg.kind === "bad") {
2442
+ issues.push({
2443
+ severity: "warning",
2444
+ code: "nudo:invariant-invalid",
2445
+ message: `${name}: malformed @nudo:invariant '${seg.raw}' (expected <name><path> <rel> <number>; path = [*] or .member steps)`,
2446
+ actual: seg.raw,
2447
+ expected: "<name><path> <rel> <number>",
2448
+ suggestion: "loop-carried bounds: @nudo:invariant dp[*][*] >= 0 \u2014 scalar: @nudo:invariant last >= 1",
2449
+ fn: name
2450
+ });
2451
+ continue;
2452
+ }
2453
+ if (!bindable.has(seg.name)) {
2454
+ issues.push({
2455
+ severity: "warning",
2456
+ code: "nudo:invariant-invalid",
2457
+ message: `${name}: @nudo:invariant name not bound as a parameter or top-level local (${seg.name})`,
2458
+ actual: seg.name,
2459
+ expected: surface || "(no params/locals)",
2460
+ suggestion: `reference one of: ${surface || "(no params or top-level locals)"}`,
2461
+ fn: name
2462
+ });
2463
+ continue;
2464
+ }
2465
+ specs.push({ name: seg.name, path: seg.path, rel: seg.rel, value: seg.value });
2466
+ display.push(seg.display);
2467
+ }
2468
+ if (specs.length === 0) return void 0;
2469
+ return { specs, display };
2470
+ }
2471
+ var OPAQUE_EVIDENCE_HINT = "add @nudo:case or a call site";
2472
+ var OPAQUE_ENV_HINT = "declare the runtime env to type native modules: '/// @nudo:env node' (or web/es) \u2014 see guides/env-harvest.md";
2473
+ function isBuiltinModuleSpec(spec) {
2474
+ if (spec.startsWith(".") || spec.startsWith("/")) return false;
2475
+ const base = (spec.startsWith("node:") ? spec.slice("node:".length) : spec).split("/")[0];
2476
+ return base.length > 0 && isBuiltin(base);
2477
+ }
2416
2478
  function checkSourceInner(filePath, source, file, phi, opts, issues, signatures, names, truncated, identityOpts = opts, depsFp, sidecarFp) {
2417
2479
  const hasRefineDirective = stripStringsKeepComments(source).includes("@nudo:contract");
2418
2480
  const entryNames = localNamedExports(source);
2419
2481
  const exportedNames = sidecarFp !== void 0 ? entryNames : void 0;
2420
2482
  const entryThrowsMode = opts.entryThrows ?? "error";
2421
2483
  const ignoreThrows = opts.ignoreThrows;
2484
+ const opaqueEnvHint = Object.keys(opts.inject?.envGlobals ?? {}).length === 0 && extractFileEnvNames(source).length === 0 && extractAllLoadSpecs(source).some(isBuiltinModuleSpec);
2422
2485
  const driftCandidates = [];
2423
2486
  const returnContracts = /* @__PURE__ */ new Map();
2424
2487
  const returnViolated = /* @__PURE__ */ new Set();
@@ -2441,7 +2504,11 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
2441
2504
  };
2442
2505
  for (const name of names) {
2443
2506
  const assumePhi = assumePremisesPhiOf(name, source, file, issues);
2444
- const runSeeded = (body) => assumePhi === void 0 ? body() : withAssumePremises(assumePhi.phi, body);
2507
+ const inv = invariantSpecsOf(name, source, file, issues);
2508
+ const runSeeded = (body) => {
2509
+ if (assumePhi === void 0 && inv === void 0) return body();
2510
+ return withAssumePremises(assumePhi?.phi ?? pTrue, () => withInvariantSpecs(inv?.specs ?? [], body));
2511
+ };
2445
2512
  if (opts.skips?.has(name)) {
2446
2513
  const declared = opts.skips.get(name) ?? null;
2447
2514
  const meta = extractFn(source, name, file);
@@ -2676,7 +2743,8 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
2676
2743
  display,
2677
2744
  eff.returns.constraint,
2678
2745
  g.symbolic,
2679
- assumePhi?.display
2746
+ assumePhi?.display,
2747
+ inv?.display
2680
2748
  );
2681
2749
  if (retIssues.some((i) => i.severity === "error")) returnViolated.add(name);
2682
2750
  issues.push(...retIssues);
@@ -2694,7 +2762,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
2694
2762
  severity: "info",
2695
2763
  code: "nudo:opaque-result",
2696
2764
  message: `${name}(...): conf=opaque (path not covered, or native)`,
2697
- suggestion: "add @nudo:case or a call site",
2765
+ suggestion: opaqueEnvHint ? OPAQUE_ENV_HINT : OPAQUE_EVIDENCE_HINT,
2698
2766
  fn: name
2699
2767
  });
2700
2768
  }
@@ -2877,23 +2945,33 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
2877
2945
  budget: { ...budget }
2878
2946
  };
2879
2947
  }
2880
- function checkReturnConstraint(fnName, cName, constraint, ret, assumeDisplay) {
2948
+ function checkReturnConstraint(fnName, cName, constraint, ret, assumeDisplay, invariantDisplay) {
2881
2949
  const proof = assertImplies(ret, constraint);
2882
2950
  if (proof.status === "proved") {
2951
+ const relied = [];
2883
2952
  if (assumeDisplay && assumeDisplay.length > 0) {
2884
- return [
2885
- {
2886
- severity: "info",
2887
- code: "nudo:assume-relied",
2888
- message: `${fnName}: return contract proved via @nudo:assume ${assumeDisplay.join("; ")}`,
2889
- actual: `proof relies on user lemma${assumeDisplay.length > 1 ? "s" : ""}`,
2890
- expected: "engine verifies the obligation, not the lemma itself",
2891
- suggestion: "@nudo:assume is a user-supplied axiom \u2014 falsify it with concrete @nudo:case runs (nudo test)",
2892
- fn: fnName
2893
- }
2894
- ];
2953
+ relied.push({
2954
+ severity: "info",
2955
+ code: "nudo:assume-relied",
2956
+ message: `${fnName}: return contract proved via @nudo:assume ${assumeDisplay.join("; ")}`,
2957
+ actual: `proof relies on user lemma${assumeDisplay.length > 1 ? "s" : ""}`,
2958
+ expected: "engine verifies the obligation, not the lemma itself",
2959
+ suggestion: "@nudo:assume is a user-supplied axiom \u2014 falsify it with concrete @nudo:case runs (nudo test)",
2960
+ fn: fnName
2961
+ });
2962
+ }
2963
+ if (invariantDisplay && invariantDisplay.length > 0) {
2964
+ relied.push({
2965
+ severity: "info",
2966
+ code: "nudo:invariant-relied",
2967
+ message: `${fnName}: return contract proved via @nudo:invariant ${invariantDisplay.join("; ")}`,
2968
+ actual: `proof relies on user invariant${invariantDisplay.length > 1 ? "s" : ""}`,
2969
+ expected: "engine verifies the obligation, not the loop invariant itself",
2970
+ suggestion: "@nudo:invariant is a user-supplied axiom \u2014 falsify it with concrete @nudo:case runs (nudo test)",
2971
+ fn: fnName
2972
+ });
2895
2973
  }
2896
- return [];
2974
+ return relied;
2897
2975
  }
2898
2976
  const push = (severity, actual, expected, suggestion) => [
2899
2977
  {
@@ -2921,361 +2999,6 @@ function checkReturnConstraint(fnName, cName, constraint, ret, assumeDisplay) {
2921
2999
  `${proof.reason}; add a precondition, narrow the return expression, or relax the return contract`
2922
3000
  );
2923
3001
  }
2924
-
2925
- // src/algebra/check-report.ts
2926
- function actionsForIssue(i) {
2927
- const draft = {
2928
- kind: "draft",
2929
- command: "nudo contract --draft",
2930
- label: "emit a sidecar draft you can edit"
2931
- };
2932
- switch (i.code) {
2933
- case "nudo:constraint-violated":
2934
- return [
2935
- {
2936
- kind: "callsite",
2937
- label: "use a value satisfying the constraint",
2938
- ...i.expected ? { hint: i.expected } : {}
2939
- },
2940
- {
2941
- kind: "relax",
2942
- label: "relax the precondition (edit *.nudo.js / @nudo:contract)",
2943
- ...i.expected ? { hint: i.expected } : {}
2944
- },
2945
- draft
2946
- ];
2947
- case "nudo:assign-mismatch":
2948
- return [
2949
- {
2950
- kind: "callsite",
2951
- label: "align the assigned value with the existing shape",
2952
- ...i.expected ? { hint: i.expected } : {}
2953
- },
2954
- draft
2955
- ];
2956
- case "nudo:entry-may-throw":
2957
- return [
2958
- {
2959
- kind: "draft",
2960
- command: "nudo contract --draft",
2961
- label: "narrow the entry with a sidecar fn({ \u2026 }) param contract \u2014 most honest fix for helpers (also tightens the signature)",
2962
- ...i.fn ? { hint: i.fn } : {}
2963
- },
2964
- {
2965
- kind: "relax",
2966
- label: "declare intentional fail-fast: @nudo:throws Error (or case `!! throws`)",
2967
- ...i.fn ? { hint: i.fn } : {}
2968
- },
2969
- {
2970
- kind: "info",
2971
- label: "or refine / guard / try-catch the entry so it is total"
2972
- },
2973
- {
2974
- kind: "ignore-throws",
2975
- command: "nudo check --ignore-throws TypeError",
2976
- label: "migration switch: ignore this throw class on L2"
2977
- }
2978
- ];
2979
- case "nudo:unproven-return":
2980
- return [
2981
- {
2982
- kind: "relax",
2983
- label: `relax the return contract${i.fn ? ` of ${i.fn}` : ""} to the inferred surface (review the diff \u2014 not a silent green)`,
2984
- ...i.expected ? { hint: i.expected } : {}
2985
- },
2986
- draft,
2987
- {
2988
- kind: "info",
2989
- label: "or narrow the return expression / add a precondition so the bound is implied"
2990
- }
2991
- ];
2992
- case "nudo:unknown-inference":
2993
- case "nudo:opaque-result":
2994
- return [
2995
- { kind: "mock", label: "pin the native/unknown face with @nudo:mock" },
2996
- { kind: "assume", command: "nudo check --assume", label: "state preconditions via --assume" },
2997
- { kind: "info", label: "or add a call site / @nudo:case for evidence" }
2998
- ];
2999
- case "nudo:arg-opaque":
3000
- case "nudo:constraint-unproven":
3001
- return [
3002
- { kind: "assume", command: "nudo check --assume", label: "state preconditions via --assume" },
3003
- { kind: "info", label: "add a call site or @nudo:case" }
3004
- ];
3005
- case "nudo:fork-truncated":
3006
- case "nudo:promise-micro-truncated":
3007
- case "nudo:promise-micro-error":
3008
- return [
3009
- {
3010
- kind: "info",
3011
- label: "results widened to unknown \u2014 raise budget (@nudo:budget forks/calls/depth on the function) or narrow control flow"
3012
- }
3013
- ];
3014
- case "nudo:loop-truncated":
3015
- return [
3016
- {
3017
- kind: "info",
3018
- label: "loop state widened \u2014 the exit condition stayed concrete-true past the iteration cap; make the bound data-dependent (abstract) or pin the result with @nudo:case / @nudo:contract"
3019
- }
3020
- ];
3021
- case "nudo:recursion-truncated":
3022
- return [
3023
- {
3024
- kind: "info",
3025
- label: i.reason === "cycle" ? "call cycle: same abstract arguments already on the stack \u2014 raising @nudo:budget cannot lift it; make the arguments vary per recursion level or declare @nudo:contract" : i.reason === "depth" || i.reason === "calls" || i.reason === "budget" ? `results widened to unknown \u2014 raise budget (@nudo:budget ${i.reason === "depth" ? "depth" : i.reason === "calls" ? "calls" : "calls/depth"} on the function) or narrow the recursion` : "results widened to unknown \u2014 raise budget (@nudo:budget calls/depth on the function) or narrow control flow"
3026
- }
3027
- ];
3028
- case "nudo:host-effect-blocked":
3029
- return [
3030
- { kind: "mock", label: "pin the host API with @nudo:mock / @nudo:env" },
3031
- { kind: "info", label: "results widened to unknown \u2014 or feed the value in from a call site" }
3032
- ];
3033
- case "nudo:interface-drift":
3034
- return [
3035
- {
3036
- kind: "emit",
3037
- command: "nudo contract --emit",
3038
- label: "refresh the @generated segment"
3039
- }
3040
- ];
3041
- case "nudo:dual-entry":
3042
- return [
3043
- {
3044
- kind: "info",
3045
- label: "browser/node records do not cross files \u2014 analyze the entry you ship; mock or skip the other variant"
3046
- }
3047
- ];
3048
- default:
3049
- return i.suggestion ? [{ kind: "info", label: i.suggestion }] : [];
3050
- }
3051
- }
3052
- function serializeCheckJson(r) {
3053
- return {
3054
- version: 1,
3055
- file: r.file,
3056
- ok: r.ok,
3057
- summary: { ...r.summary },
3058
- signatures: r.signatures.map((s) => ({
3059
- name: s.name,
3060
- params: [...s.params],
3061
- ...s.paramTypes ? { paramTypes: [...s.paramTypes] } : {},
3062
- display: s.display,
3063
- detail: s.detail,
3064
- conf: s.conf,
3065
- abs: formatAbs(s.abs),
3066
- ...s.throws ? { throws: s.throws } : {},
3067
- ...s.entry ? { entry: true } : {}
3068
- })),
3069
- issues: r.issues.map((i) => {
3070
- const acts = i.actions ?? actionsForIssue(i);
3071
- return {
3072
- severity: i.severity,
3073
- code: i.code,
3074
- message: i.message,
3075
- ...i.fn !== void 0 ? { fn: i.fn } : {},
3076
- ...i.line !== void 0 ? { line: i.line } : {},
3077
- ...i.column !== void 0 ? { column: i.column } : {},
3078
- ...i.actual !== void 0 ? { actual: i.actual } : {},
3079
- ...i.expected !== void 0 ? { expected: i.expected } : {},
3080
- ...i.suggestion !== void 0 ? { suggestion: i.suggestion } : {},
3081
- ...i.reason !== void 0 ? { reason: i.reason } : {},
3082
- ...acts.length > 0 ? { actions: acts } : {}
3083
- };
3084
- }),
3085
- ...r.budget ? { budget: { ...r.budget } } : {}
3086
- };
3087
- }
3088
- function serializeCheckJsonMulti(reports) {
3089
- const summary = {
3090
- errors: 0,
3091
- warnings: 0,
3092
- infos: 0,
3093
- functions: 0,
3094
- files: reports.length
3095
- };
3096
- let calls = 0;
3097
- let forks = 0;
3098
- let maxCalls = 0;
3099
- let maxForks = 0;
3100
- let callTruncated = false;
3101
- let forkTruncated = false;
3102
- let evalCalls = 0;
3103
- let maxEvalCalls = 0;
3104
- let evalCallTruncated = false;
3105
- let cycleTruncated = false;
3106
- let loopTruncated = false;
3107
- const reasons = /* @__PURE__ */ new Set();
3108
- for (const r of reports) {
3109
- summary.errors += r.summary.errors;
3110
- summary.warnings += r.summary.warnings;
3111
- summary.infos += r.summary.infos;
3112
- summary.functions += r.summary.functions;
3113
- if (r.budget) {
3114
- calls += r.budget.calls;
3115
- forks += r.budget.forks;
3116
- maxCalls = Math.max(maxCalls, r.budget.maxCalls);
3117
- maxForks = Math.max(maxForks, r.budget.maxForks);
3118
- callTruncated = callTruncated || r.budget.callTruncated;
3119
- forkTruncated = forkTruncated || r.budget.forkTruncated;
3120
- evalCalls += r.budget.evalCalls ?? 0;
3121
- maxEvalCalls = Math.max(maxEvalCalls, r.budget.maxEvalCalls ?? 0);
3122
- evalCallTruncated = evalCallTruncated || (r.budget.evalCallTruncated ?? false);
3123
- cycleTruncated = cycleTruncated || (r.budget.cycleTruncated ?? false);
3124
- loopTruncated = loopTruncated || (r.budget.loopTruncated ?? false);
3125
- for (const x of r.budget.reasons ?? []) reasons.add(x);
3126
- }
3127
- }
3128
- const budgetTruncated = callTruncated || evalCallTruncated || forkTruncated || loopTruncated;
3129
- return {
3130
- version: 1,
3131
- kind: "multi",
3132
- ok: reports.every((r) => r.ok),
3133
- summary: {
3134
- ...summary,
3135
- ...budgetTruncated ? { budgetTruncated: true } : {}
3136
- },
3137
- ...budgetTruncated || reports.some((r) => r.budget) ? {
3138
- budget: {
3139
- truncated: budgetTruncated,
3140
- callTruncated,
3141
- forkTruncated,
3142
- calls,
3143
- maxCalls,
3144
- forks,
3145
- maxForks,
3146
- evalCalls,
3147
- maxEvalCalls,
3148
- evalCallTruncated,
3149
- cycleTruncated,
3150
- loopTruncated,
3151
- reasons: [...reasons].sort()
3152
- }
3153
- } : {},
3154
- reports
3155
- };
3156
- }
3157
- var CONTRACT_FIX_CODES = /* @__PURE__ */ new Set([
3158
- "nudo:constraint-violated",
3159
- "nudo:assign-mismatch",
3160
- "nudo:arg-structure",
3161
- "nudo:interface-domain-exceeds",
3162
- "nudo:entry-may-throw",
3163
- "nudo:missing-slot"
3164
- ]);
3165
- function formatCheckReport(r, opts = {}) {
3166
- const lines = [];
3167
- lines.push(`nudo check ${r.file}`);
3168
- lines.push(r.ok ? "OK" : "FAILED");
3169
- lines.push(
3170
- ` ${r.summary.errors} error \xB7 ${r.summary.warnings} warning \xB7 ${r.summary.infos} info \xB7 ${r.summary.functions} fn`
3171
- );
3172
- if (r.budget?.truncated) {
3173
- const onlyLoop = r.budget.loopTruncated && !r.budget.callTruncated && !r.budget.evalCallTruncated && !r.budget.forkTruncated;
3174
- lines.push("");
3175
- lines.push("budget");
3176
- lines.push(
3177
- ` truncated calls ${r.budget.calls}/${r.budget.maxCalls} \xB7 eval calls ${r.budget.evalCalls}/${r.budget.maxEvalCalls} \xB7 forks ${r.budget.forks}/${r.budget.maxForks}${r.budget.callTruncated ? " (calls)" : ""}${r.budget.evalCallTruncated ? " (eval calls)" : ""}${r.budget.forkTruncated ? " (forks)" : ""}${r.budget.loopTruncated ? " (loop iters)" : ""}`
3178
- );
3179
- lines.push(
3180
- r.budget.cycleTruncated ? ` \u2192 some results widened to unknown (not a contract failure) \u2014 includes call cycles (same abstract args; a budget cannot lift them)` : onlyLoop ? ` \u2192 loop state widened (iteration budget, not a contract failure) \u2014 @nudo:budget does not apply; make the bound data-dependent or pin with @nudo:case / @nudo:contract` : ` \u2192 some results widened to unknown (not a contract failure) \u2014 raise @nudo:budget / nudo.analysis.maxForks or simplify recursion`
3181
- );
3182
- }
3183
- if (r.signatures.length > 0) {
3184
- lines.push("");
3185
- lines.push("signatures");
3186
- for (const s of r.signatures) {
3187
- const paramStr = s.paramTypes && s.paramTypes.length > 0 ? s.params.map((p, i) => `${p}: ${s.paramTypes[i] ?? "any"}`).join(", ") : s.params.join(", ");
3188
- const throwsStr = s.throws ? ` throws ${s.throws}` : "";
3189
- const displayBase = s.display.replace(/\s+throws\s+[\s\S]+$/, "").trim();
3190
- const shapeK = s.abs?.shape?.k;
3191
- const looksLikeFakeAny = shapeK === "any" && displayBase.length > 0 && !/^any(\b|$)/.test(displayBase);
3192
- const retStr = !looksLikeFakeAny && shapeK ? formatShape(s.abs) || displayBase.replace(/\s+#\w+$/, "").trim() : displayBase.replace(/\s+#\w+$/, "").trim();
3193
- lines.push(` ${s.name}(${paramStr}) => ${retStr}${throwsStr}`);
3194
- if (opts.verbose) {
3195
- for (const ln of s.detail.split("\n").slice(1)) {
3196
- lines.push(` ${ln}`);
3197
- }
3198
- }
3199
- }
3200
- }
3201
- if (r.issues.length === 0) {
3202
- lines.push("");
3203
- lines.push("(no issues)");
3204
- } else {
3205
- lines.push("");
3206
- lines.push("issues");
3207
- for (const i of r.issues) {
3208
- const loc = i.line != null ? `L${i.line}` : "";
3209
- const head = [i.severity.toUpperCase(), loc, i.fn].filter(Boolean).join(" ");
3210
- lines.push(` [${head}] ${i.message} (${i.code})`);
3211
- const showDetail = i.severity === "error" || opts.verbose;
3212
- if (showDetail && i.actual) lines.push(` actual: ${i.actual}`);
3213
- if (showDetail && i.expected) lines.push(` expected: ${i.expected}`);
3214
- if (i.suggestion) {
3215
- lines.push(` \u2192 ${i.suggestion}`);
3216
- } else if (CONTRACT_FIX_CODES.has(i.code)) {
3217
- lines.push(` \u2192 add a refine / sidecar contract, or fix the call-site value`);
3218
- }
3219
- if (CONTRACT_FIX_CODES.has(i.code)) {
3220
- lines.push(` fix: nudo contract --draft (emit a sidecar draft you can edit)`);
3221
- }
3222
- }
3223
- }
3224
- return lines.join("\n");
3225
- }
3226
- function escapeGhaData(s) {
3227
- return s.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
3228
- }
3229
- function escapeGhaProp(s) {
3230
- return escapeGhaData(s).replace(/:/g, "%3A").replace(/,/g, "%2C");
3231
- }
3232
- function formatGithubAnnotations(r, opts = {}) {
3233
- const lines = [];
3234
- let file = r.file;
3235
- if (opts.workspaceRoot && file.startsWith(opts.workspaceRoot)) {
3236
- file = file.slice(opts.workspaceRoot.length).replace(/^[/\\]/, "");
3237
- }
3238
- for (const i of r.issues) {
3239
- const level = i.severity === "error" ? "error" : i.severity === "warning" ? "warning" : "notice";
3240
- const line = i.line ?? 1;
3241
- const col = (i.column ?? 0) + 1;
3242
- const title = escapeGhaProp(i.code);
3243
- const msgBits = [i.message];
3244
- if (i.actual && i.expected) msgBits.push(`${i.actual} \u22AD ${i.expected}`);
3245
- else if (i.suggestion) msgBits.push(i.suggestion);
3246
- if (CONTRACT_FIX_CODES.has(i.code)) {
3247
- msgBits.push(`fix: nudo contract --draft`);
3248
- }
3249
- const msg = escapeGhaData(msgBits.join(" \xB7 "));
3250
- const props = [
3251
- `file=${escapeGhaProp(file)}`,
3252
- `line=${line}`,
3253
- `col=${col}`,
3254
- `title=${title}`
3255
- ].join(",");
3256
- lines.push(`::${level} ${props}::${msg}`);
3257
- }
3258
- return lines.join("\n");
3259
- }
3260
- function formatGitlabCodeQuality(r, opts = {}) {
3261
- let file = r.file;
3262
- if (opts.workspaceRoot && file.startsWith(opts.workspaceRoot)) {
3263
- file = file.slice(opts.workspaceRoot.length).replace(/^[/\\]/, "");
3264
- }
3265
- return r.issues.map((i) => {
3266
- const begin = i.line ?? 1;
3267
- const severity = i.severity === "error" ? "major" : i.severity === "warning" ? "minor" : "info";
3268
- const description = i.actual && i.expected ? `${i.message} (${i.actual} \u22AD ${i.expected})` : i.message;
3269
- const fingerprint = `${file}:${i.code}:${begin}:${i.message}`;
3270
- return {
3271
- description,
3272
- check_name: i.code,
3273
- fingerprint,
3274
- severity,
3275
- location: { path: file, lines: { begin } }
3276
- };
3277
- });
3278
- }
3279
3002
  export {
3280
3003
  $add,
3281
3004
  $arguments,
@@ -1,7 +1,7 @@
1
- import { I as InterfaceSource, a as InterfaceTierOpts, p as parseSource, C as CheckIssue } from './projection-budget-DPPRBOLp.js';
2
- export { L as LeakBudget, b as LoadDepsFingerprint, M as MAX_PROMISE_MICROS, P as PROJECTION_MAX_DEPTH, c as ProjectionBudget, d as ProjectionStopReason, e as defaultLeakBudget, f as exceedsBudget, g as exportedNameOfLocal, h as extractAllLoadSpecs, i as getPromiseMicrosLength, l as leakIfNeeded, j as loadModuleDepsFingerprint, m as maybeLeak, n as normPath, r as resetLeakCounter, k as resolveDepPath, s as sidecarSpecsOf, o as stablePathKey, q as stablePathKeyGraph, t as termDepth, u as termNodes } from './projection-budget-DPPRBOLp.js';
3
- import { A as Abs, T as Term, c as AbsModuleExports, d as AstEnv } from './run-DhSqP3in.js';
4
- export { $ as $tryDigestSoft, e as $tryMarkSoft, f as $tryReleaseSoft, E as ERROR_FAMILY, g as EvalFallbackStats, M as MayThrowEffect, h as absLit, i as absNumLit, j as absStrLit, k as allAbsStr, l as clearPureCallMemo, m as clearPureMemo, n as enterEvalCallBudgetSession, o as errorTypeAbs, p as exitEvalCallBudgetSession, q as filterDeclaredThrows, r as filterGateThrows, s as filterIgnoredThrows, t as flushMayThrowEffects, u as formatThrowsAbs, v as getEvalCallBudgetState, w as getEvalFallbackStats, x as getMayThrowCollector, y as isThrowsIgnored, z as mayThrowEffectsToAbs, B as orphanMayThrowEffects, D as popMayThrowFrame, F as pushMayThrowFrame, G as recordMayThrow, H as resetEvalFallbackStats, I as runWithMayThrowSession, J as setMayThrowCollector, K as throwAbsToKinds, N as throwsKindCovered, O as withEvalBudgetOverride } from './run-DhSqP3in.js';
1
+ import { I as InterfaceSource, a as InterfaceTierOpts, p as parseSource, C as CheckIssue } from './projection-budget-07VnMktV.js';
2
+ export { L as LeakBudget, b as LoadDepsFingerprint, M as MAX_PROMISE_MICROS, P as PROJECTION_MAX_DEPTH, c as ProjectionBudget, d as ProjectionStopReason, e as budgetAttributionSegs, f as defaultLeakBudget, g as exceedsBudget, h as exportedNameOfLocal, i as extractAllLoadSpecs, j as getPromiseMicrosLength, l as leakIfNeeded, k as loadModuleDepsFingerprint, m as maybeLeak, n as normPath, r as resetLeakCounter, o as resolveDepPath, s as sidecarSpecsOf, q as stablePathKey, t as stablePathKeyGraph, u as termDepth, v as termNodes } from './projection-budget-07VnMktV.js';
3
+ import { A as Abs, T as Term, c as AbsModuleExports, d as AstEnv } from './run-DfloWX6i.js';
4
+ export { $ as $tryDigestSoft, e as $tryMarkSoft, f as $tryReleaseSoft, E as ERROR_FAMILY, g as EvalFallbackStats, M as MayThrowEffect, h as absLit, i as absNumLit, j as absStrLit, k as allAbsStr, l as clearPureCallMemo, m as clearPureMemo, n as enterEvalCallBudgetSession, o as errorTypeAbs, p as exitEvalCallBudgetSession, q as filterDeclaredThrows, r as filterGateThrows, s as filterIgnoredThrows, t as flushMayThrowEffects, u as formatThrowsAbs, v as getEvalCallBudgetState, w as getEvalFallbackStats, x as getMayThrowCollector, y as isThrowsIgnored, z as mayThrowEffectsToAbs, B as orphanMayThrowEffects, D as popMayThrowFrame, F as pushMayThrowFrame, G as recordMayThrow, H as resetEvalFallbackStats, I as runWithMayThrowSession, J as setMayThrowCollector, K as throwAbsToKinds, N as throwsKindCovered, O as withEvalBudgetOverride } from './run-DfloWX6i.js';
5
5
  import { File, Node } from '@babel/types';
6
6
 
7
7
  /**
@@ -89,6 +89,18 @@ type AbsBudgetStats = {
89
89
  loopTruncated: boolean;
90
90
  /** 调用截断原因集合(A+B;稳定排序,进 JSON) */
91
91
  reasons: AbsTruncationReason[];
92
+ /** A 路径(enterCall 抽象调用)本轮截断原因——上屏归因用(与合并集 reasons 分流) */
93
+ callReasons: AbsTruncationReason[];
94
+ /** A 路径最大进入深度;depth 截断时 = 触发时深度(== 触发时生效上限) */
95
+ depth: number;
96
+ /** A 路径 depth 上限;depth 截断时 = 触发时生效上限(@nudo:budget 覆盖亦如实) */
97
+ maxDepth: number;
98
+ /** B 路径($callNamed 求值调用)本轮截断原因 */
99
+ evalReasons: AbsTruncationReason[];
100
+ /** B 路径最大进入深度;depth 截断时 = 触发时深度(== 触发时生效上限) */
101
+ evalDepth: number;
102
+ /** B 路径 depth 上限;depth 截断时 = 触发时生效上限(@nudo:budget 覆盖亦如实) */
103
+ maxEvalDepth: number;
92
104
  };
93
105
  declare function getAbsCallBudgetStats(): AbsBudgetStats;
94
106
  /** 宿主入口(runTranspiled / callTranspiledExportFull / check)前重置 */
@@ -170,6 +182,10 @@ declare function resetEvalForkBudget(): void;
170
182
  /** $fork 入口:超限放弃该分支(调用方返回 unknown = 最保守,安全)。 */
171
183
  declare function bumpEvalForkBudget(): boolean;
172
184
 
185
+ /** 每轮模块图求值入口清空:上一轮 refresh 闭包只指向已死模块作用域,
186
+ * 语义无害但滞留膨胀;新图重新注册(evalAbsModuleGraph 入口调用)。 */
187
+ declare function clearLiveImportRefreshers(): void;
188
+
173
189
  /**
174
190
  * 可 ALS 隔离的模块级可变槽。
175
191
  * 语义:`get`/`set` 作用于当前最内层 store(无作用域 = fallback);
@@ -774,4 +790,4 @@ declare function runWithEvalMissingSlot<T>(enabled: boolean, body: () => T): T;
774
790
  */
775
791
  declare function noteObjSlotMissing(recv: Abs | undefined, name: string, loc?: [number, number]): boolean;
776
792
 
777
- export { type AbsBudgetStats, type AbsInlay, type ArgDerivation, type ClassDef, type CollectAbsInlaysOpts, type CollectorScopeParticipant, type DerivationNode, type DiagChannel, type EvalMemberDiag, FORK_TRUNCATION_LABEL, type FnFp, HOST_EFFECT_LABEL_PREFIX, type InjectedDomainEvidenceOpts, type InjectedDomainRecord, MAX_CALL_DEPTH, MAX_EVAL_TOTAL_FORKS, MAX_TOTAL_CALLS, type MethodDef, OBJECT_PROTO_NAMES, PROMISE_MICRO_ERROR_LABEL, PROMISE_MICRO_OVERFLOW_LABEL, type ScopedDiagChannel, type ScopedSlot, type StringLiteralSpan, type TemplateMeta, type TemplatePartDesc, type TemplatePartView, type TemplatePredicateDecision, abortDerivationSession, absTemplateViews, allFixedTextOfViews, anyMemberResult, awaitAbs, beginDerivationSession, bumpEvalForkBudget, callBudgetKey, canSkipLiteralCallScan, checkInjectedDomainEvidence, classChainNames, classFromMethods, coerceAsyncReturn, collectAbsInlays, concatString, createDiagChannel, createScopedDiagChannel, createScopedSlot, createTemplateAbs, decideEndsWith, decideIncludes, decideStartsWith, defineClass, definitelyUncallableMember, denoteGuard, derivationChain, endDerivationSession, enterCall, escapeTemplateTypeFixed, exitCall, fixedLengthOfViews, fnFingerprints, formatObjectKey, formatTemplateNameViews, generalizeSourceKeyPart, getAbsCallBudgetStats, getAbsOrigin, getClass, getClassChain, getDerivation, getEvalForkBudgetLimit, getEvalForkCount, getFnNameAndBodies, hasDerivationSession, hashSource, instanceOf, instantiateClass, isEvalMissingSlotEnabled, isJsBindingIdent, isJsIdent, isNullishAbs, isTemplateLike, knownPrefixOfViews, knownSuffixOfViews, listTopFunctions, lookupMethod, lookupMethodWithOwner, lookupSuperMethod, maskCommentsAndStrings, mergeAdjacentFixedViews, noteAbsTruncation, noteAnyMemberMayThrow, noteDerivationAdd, noteDerivationJoin, noteEvalForkTruncation, noteHostEffectBlocked, noteMemberDispatchMiss, noteNullishMemberThrows, noteObjSlotMissing, notePrimMemberMissing, noteUnknownMemberMissing, popCallLoc, projectBrand, projectDerivationDsl, pushCallLoc, recordMemberDiag, registerCollectorScopeParticipant, resetAbsCallBudget, resetEvalForkBudget, resetFnFpCache, resetHashSourceCache, runWithCollectorScope, runWithEvalMissingSlot, safeMemberAccess, sanitizeCommentText, scanStringLiterals, setAbsTruncationCollector, setDerivation, setDerivationCollector, setEvalForkBudgetLimit, setEvalMissingSlotEnabled, setMemberDiagCollector, sourceHasCjsExports, sourceHasModuleDependency, sourceHasRequireCall, stableAnalyzeKeySource, stableCallId, stripCommentsAndStrings, stripStringsKeepComments, superNameOf, tagAbsOrigin, tagDerivationRoot, templateMatchesValue, templatePartsOf, termKey, toJsBindingIdent, truncatedAbs, viewTemplateParts, withFnBudgetOverride, wrapPromise };
793
+ export { type AbsBudgetStats, type AbsInlay, type ArgDerivation, type ClassDef, type CollectAbsInlaysOpts, type CollectorScopeParticipant, type DerivationNode, type DiagChannel, type EvalMemberDiag, FORK_TRUNCATION_LABEL, type FnFp, HOST_EFFECT_LABEL_PREFIX, type InjectedDomainEvidenceOpts, type InjectedDomainRecord, MAX_CALL_DEPTH, MAX_EVAL_TOTAL_FORKS, MAX_TOTAL_CALLS, type MethodDef, OBJECT_PROTO_NAMES, PROMISE_MICRO_ERROR_LABEL, PROMISE_MICRO_OVERFLOW_LABEL, type ScopedDiagChannel, type ScopedSlot, type StringLiteralSpan, type TemplateMeta, type TemplatePartDesc, type TemplatePartView, type TemplatePredicateDecision, abortDerivationSession, absTemplateViews, allFixedTextOfViews, anyMemberResult, awaitAbs, beginDerivationSession, bumpEvalForkBudget, callBudgetKey, canSkipLiteralCallScan, checkInjectedDomainEvidence, classChainNames, classFromMethods, clearLiveImportRefreshers, coerceAsyncReturn, collectAbsInlays, concatString, createDiagChannel, createScopedDiagChannel, createScopedSlot, createTemplateAbs, decideEndsWith, decideIncludes, decideStartsWith, defineClass, definitelyUncallableMember, denoteGuard, derivationChain, endDerivationSession, enterCall, escapeTemplateTypeFixed, exitCall, fixedLengthOfViews, fnFingerprints, formatObjectKey, formatTemplateNameViews, generalizeSourceKeyPart, getAbsCallBudgetStats, getAbsOrigin, getClass, getClassChain, getDerivation, getEvalForkBudgetLimit, getEvalForkCount, getFnNameAndBodies, hasDerivationSession, hashSource, instanceOf, instantiateClass, isEvalMissingSlotEnabled, isJsBindingIdent, isJsIdent, isNullishAbs, isTemplateLike, knownPrefixOfViews, knownSuffixOfViews, listTopFunctions, lookupMethod, lookupMethodWithOwner, lookupSuperMethod, maskCommentsAndStrings, mergeAdjacentFixedViews, noteAbsTruncation, noteAnyMemberMayThrow, noteDerivationAdd, noteDerivationJoin, noteEvalForkTruncation, noteHostEffectBlocked, noteMemberDispatchMiss, noteNullishMemberThrows, noteObjSlotMissing, notePrimMemberMissing, noteUnknownMemberMissing, popCallLoc, projectBrand, projectDerivationDsl, pushCallLoc, recordMemberDiag, registerCollectorScopeParticipant, resetAbsCallBudget, resetEvalForkBudget, resetFnFpCache, resetHashSourceCache, runWithCollectorScope, runWithEvalMissingSlot, safeMemberAccess, sanitizeCommentText, scanStringLiterals, setAbsTruncationCollector, setDerivation, setDerivationCollector, setEvalForkBudgetLimit, setEvalMissingSlotEnabled, setMemberDiagCollector, sourceHasCjsExports, sourceHasModuleDependency, sourceHasRequireCall, stableAnalyzeKeySource, stableCallId, stripCommentsAndStrings, stripStringsKeepComments, superNameOf, tagAbsOrigin, tagDerivationRoot, templateMatchesValue, templatePartsOf, termKey, toJsBindingIdent, truncatedAbs, viewTemplateParts, withFnBudgetOverride, wrapPromise };
package/dist/internal.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import {
2
+ budgetAttributionSegs,
2
3
  canSkipLiteralCallScan,
3
4
  checkInjectedDomainEvidence,
4
5
  createDiagChannel,
@@ -21,7 +22,7 @@ import {
21
22
  stableAnalyzeKeySource,
22
23
  stablePathKey,
23
24
  stablePathKeyGraph
24
- } from "./chunk-V2XC3V72.js";
25
+ } from "./chunk-4GZPH6YL.js";
25
26
  import {
26
27
  $tryDigestSoft,
27
28
  $tryMarkSoft,
@@ -52,6 +53,7 @@ import {
52
53
  callBudgetKey,
53
54
  classChainNames,
54
55
  classFromMethods,
56
+ clearLiveImportRefreshers,
55
57
  clearPureCallMemo,
56
58
  clearPureMemo,
57
59
  coerceAsyncReturn,
@@ -169,7 +171,7 @@ import {
169
171
  withEvalBudgetOverride,
170
172
  withFnBudgetOverride,
171
173
  wrapPromise
172
- } from "./chunk-A2PLG2QV.js";
174
+ } from "./chunk-JMOQEJTD.js";
173
175
 
174
176
  // src/algebra/inlay.ts
175
177
  function listFunctions(source) {
@@ -632,12 +634,14 @@ export {
632
634
  anyMemberResult,
633
635
  awaitAbs,
634
636
  beginDerivationSession,
637
+ budgetAttributionSegs,
635
638
  bumpEvalForkBudget,
636
639
  callBudgetKey,
637
640
  canSkipLiteralCallScan,
638
641
  checkInjectedDomainEvidence,
639
642
  classChainNames,
640
643
  classFromMethods,
644
+ clearLiveImportRefreshers,
641
645
  clearPureCallMemo,
642
646
  clearPureMemo,
643
647
  coerceAsyncReturn,