@chenglou/freerange 0.0.1 → 0.0.3

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/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.0.3 - 2026-07-28
6
+
7
+ - Analyze arrow functions and function expressions assigned directly to top-level `const` names.
8
+ - Preserve type narrowing through `&&` and `||` expressions.
9
+ - Analyze each `Math.random()` call as a fresh number from zero up to, but not including, one.
10
+ - Avoid quadratic work when tracking values through deep chains of same-file function calls.
11
+ - Consolidate analysis limits and practical refactoring examples in the README.
12
+
13
+ ## 0.0.2 - 2026-07-21
14
+
15
+ - Add node support (#2)
16
+
5
17
  ## 0.0.1 - 2026-07-20
6
18
 
7
19
  Initial public release of `@chenglou/freerange`.
package/README.md CHANGED
@@ -7,12 +7,12 @@ Freerange shows you the range of every `number` in your TypeScript codebase, let
7
7
  - **Fast**. Uses a negligible fraction of TypeScript's analysis time.
8
8
  - **Robust**. Adversarially tested by agents against thousands of edge cases.
9
9
 
10
- Freerange is deliberately designed for code that can be refactored, especially code written or maintained by agents. It does not try to understand every TypeScript pattern. It accepts a small, predictable subset and gives concrete guidance for moving important calculations into that subset.
10
+ Freerange is deliberately designed to cater to a useful (and growing) subset of TypeScript, and gives concrete guidance for moving important calculations into that subset, so that your code and math can meet in the middle to unlock the most proof power without much ergonomics drawbacks. AI agents are especially well-suited to refactor such code, and we highly recommend you asking them to do so. However, if you/they do find an unsupported TS feature truly valuable, please file an issue!
11
11
 
12
12
  ## Install
13
13
 
14
14
  ```sh
15
- bun install --dev @chenglou/freerange
15
+ bun install --dev @chenglou/freerange # npm install works too of course
16
16
  ```
17
17
 
18
18
  ## API
@@ -44,7 +44,7 @@ function gridItemWidth(containerWidth: number) {
44
44
  gridItemWidth(200)
45
45
  ```
46
46
 
47
- `bun fr` outputs:
47
+ `bun fr` (or `npx fr`) outputs:
48
48
 
49
49
  ```zsh
50
50
  index.ts:9:1 - error [inferred-requirement]: call to gridItemWidth violates its nonzero divisor requirement (division at index.ts:6:10)
@@ -117,45 +117,247 @@ Every plain `number` parameter, including a number field in a fixed-shape object
117
117
 
118
118
  ## Writing Analyzable TypeScript
119
119
 
120
- Freerange supports a subset of TS:
121
- - Named, synchronous top-level functions in a file; Freerange follows calls between functions in the same file
120
+ Freerange deliberately analyzes a restricted part of TypeScript. When code leaves that scope, `fr --audit` says so instead of guessing what the code does. Freerange currently supports:
121
+
122
+ - Named, synchronous top-level functions. Both `function size(...) {}` and direct `const size = (...) => ...` declarations work. Freerange follows calls between functions in the same file
122
123
  - Numbers, booleans, strings, nullable values, plain objects, tagged unions, dense arrays, and fixed tuples
123
124
  - `if`/`else`, ternaries, non-fallthrough `switch`, `&&`, `||`, `!`, `??`, `for`, `while`, and `for...of` loops
124
125
  - Arithmetic, comparisons, object field and array reads, selected `Math` operations, and `Number.isInteger`, `Number.isFinite`, and `Number.isNaN`
125
126
 
126
127
  Freerange could theoretically support a much larger subset of TS, and did before its public release. Those patterns often made numeric inference and proofs much harder and slower, however, and some questions are undecidable in general. Now that AI agents write code, we strongly recommend asking agents to refactor important calculations into shapes that Freerange analyzes well, guided by `fr --audit`. Code that is easy to analyze tends to resemble functional programming: immutable data, explicit inputs and outputs, and clean, direct control flow.
127
128
 
128
- - **Put important calculations in small named functions.** A React component, callback, or async function can call a plain synchronous helper. Keep any helper functions that Freerange needs to inspect in the same file.
129
- ```tsx
130
- export function fittedImageHeight(frameWidth: number, imageWidth: number, imageHeight: number): number {
131
- const width = Math.max(1, imageWidth)
132
- const height = Math.max(1, imageHeight)
133
- return (frameWidth * height) / width
134
- }
129
+ Use precise TypeScript types. Avoid `any`, casts, and suppression comments, and parse external data before passing it to a numeric helper. A file containing `@ts-ignore`, `@ts-expect-error`, `@ts-nocheck`, or `eval` is rejected because its declared types cannot be trusted.
130
+
131
+ Before changing an input rule, decide what the application should do. If `columnCount` must be a positive integer, either require that with leading `console.assert` calls or normalize it with `Math.max(1, Math.floor(columnCount))`. Only normalize when the application wants that runtime behavior. Audit code: `[encode-input-rule]`.
132
+
133
+ ### Numeric Analysis
134
+
135
+ For each `number`, Freerange remembers its lowest and highest possible values, whether it is an integer, whether it may be `NaN` or infinite, and at most one exact value that has been ruled out. It reasons about JavaScript floating-point numbers rather than ideal real numbers and does not use a general-purpose theorem prover.
136
+
137
+ #### No common-subexpression elimination
138
+
139
+ Freerange does not replace repeated calculations with one stored result, even when their source code is identical. This function checks one subtraction, then divides by a newly evaluated subtraction:
140
+
141
+ ```ts
142
+ export function progressBar(value: number, start: number, end: number): number {
143
+ if (end - start === 0) return 0
144
+ return (value - start) / (end - start)
145
+ }
146
+ ```
147
+
148
+ Calculate the subtraction once when the check and division must use the same result:
149
+
150
+ ```ts
151
+ export function progressBar(value: number, start: number, end: number): number {
152
+ const span = end - start
153
+ if (span === 0) return 0
154
+ return (value - start) / span
155
+ }
156
+ ```
157
+
158
+ Freerange recognizes aliases, repeated reads of the same immutable field or array position, and the same argument passed to multiple parameters. A newly evaluated calculation or function call is a new value. Audit code: `[guard-derived-value]`.
159
+
160
+ #### Ranges use inclusive endpoints
161
+
162
+ Freerange stores every range using its lowest and highest included values. JavaScript numbers are discrete, so the neighboring representable number can often express a strict endpoint: `Math.random()` is stored as `0..0.9999999999999999` and reported as `at least 0 and less than 1`. This needs no rewrite.
163
+
164
+ #### Branches merge into one continuous range
165
+
166
+ Freerange combines both branch results into one continuous range. Here `width` becomes `240..480`, which includes `300` even though neither branch returns it:
167
+
168
+ ```ts
169
+ export function previewRatio(compact: boolean): number {
170
+ const width = compact ? 240 : 480
171
+ return 100 / (width - 300)
172
+ }
173
+ ```
174
+
175
+ Keep a calculation inside each branch when it depends on the separate alternatives:
176
+
177
+ ```ts
178
+ export function previewRatio(compact: boolean): number {
179
+ if (compact) return 100 / (240 - 300)
180
+ return 100 / (480 - 300)
181
+ }
182
+ ```
183
+
184
+ A broad but safe range may need no rewrite.
185
+
186
+ #### A number remembers at most one excluded value
187
+
188
+ After `code !== 240` and `code !== 300`, Freerange may remember only the later exclusion. When an operation depends on a particular exclusion, check the value that the operation uses:
189
+
190
+ ```ts
191
+ const divisor = code - 240
192
+ if (divisor === 0) return 0
193
+ return 100 / divisor
194
+ ```
195
+
196
+ Freerange does not retain arbitrary sets such as "every number except 240 and 300."
197
+
198
+ #### No transitive reasoning between comparisons
199
+
200
+ Freerange does not combine `left <= middle` and `middle <= right` to prove `left <= right`:
201
+
202
+ ```ts
203
+ if (left > middle) throw new Error('out of order')
204
+ if (middle > right) throw new Error('out of order')
205
+ console.assert(left <= right) // unproven
206
+ ```
207
+
208
+ When one value is built from another, keep the relationship visible in that calculation:
209
+
210
+ ```ts
211
+ const gap = Math.max(0, requestedGap)
212
+ const left = navRight + gap
213
+ console.assert(navRight <= left)
214
+ ```
215
+
216
+ If the values arrive independently, Freerange may leave the relationship unproven.
217
+
218
+ #### No algebraic inversion of conditions
219
+
220
+ Freerange narrows the direct operands of a comparison but does not rearrange `width * 2 > 10` to derive `width > 5`. Check the value used by the later operation:
221
+
222
+ ```ts
223
+ export function previewScale(width: number): number {
224
+ if (width <= 5) return 1
225
+ return 100 / width
226
+ }
227
+ ```
228
+
229
+ #### No algebraic normalization
230
+
231
+ Freerange does not rewrite expressions using associativity, commutativity, or distributivity. Those rules do not always preserve JavaScript floating-point results:
232
+
233
+ ```ts
234
+ const amount = 9_007_199_254_740_992
235
+ const first = 3 + amount + 2 // 9007199254740998
236
+ const second = 1 + amount + 4 // 9007199254740996
237
+ ```
238
+
239
+ Choose the operation order whose floating-point behavior the application wants. For example, `frameWidth / (imageWidth / imageHeight)` introduces a ratio that can round to zero; `(frameWidth * imageHeight) / imageWidth` avoids that particular problem, although the two expressions can still round differently. Audit code: `[use-direct-operands]`.
240
+
241
+ #### Numeric truthiness is unsupported
242
+
243
+ Freerange does not guess what a number used as a condition means. Write `width === 0` instead of relying on a truthy or falsy number such as `width || 1`. Audit code: `[write-explicit-condition]`.
244
+
245
+ ### Functions
246
+
247
+ Put important calculations in named synchronous functions. A React component, callback, or async function can call a plain helper:
248
+
249
+ ```tsx
250
+ export function fittedImageHeight(frameWidth: number, imageWidth: number, imageHeight: number): number {
251
+ return (frameWidth * Math.max(1, imageHeight)) / Math.max(1, imageWidth)
252
+ }
253
+
254
+ function ImageCard(props: {frameWidth: number; imageWidth: number; imageHeight: number}) {
255
+ const height = fittedImageHeight(props.frameWidth, props.imageWidth, props.imageHeight)
256
+ return <img style={{height}} />
257
+ }
258
+ ```
259
+
260
+ Literal default parameters and omitted optional parameters work in supported same-file calls. Object and calculated defaults do not. Passing more arguments than the implementation declares is unsupported.
261
+
262
+ #### A function return does not include how the value was calculated
135
263
 
136
- function ImageCard(props: {frameWidth: number; imageWidth: number; imageHeight: number}) {
137
- const height = fittedImageHeight(props.frameWidth, props.imageWidth, props.imageHeight)
138
- return <img style={{height}} />
264
+ Freerange evaluates supported same-file helpers using what the caller knows. It keeps what the helper may return, including numeric ranges and object fields, but not an equation such as "this result is exactly `end - start`":
265
+
266
+ ```ts
267
+ function span(start: number, end: number): number {
268
+ return end - start
269
+ }
270
+
271
+ export function progressBar(value: number, start: number, end: number): number {
272
+ return (value - start) / span(start, end)
273
+ }
274
+ ```
275
+
276
+ When the caller needs that exact calculation, calculate and check it in the caller, then pass the result to a helper. The same limitation applies to booleans: `true` from `isValidIndex(values, index)` does not tell the caller which checks made it true, so write those checks where they protect the array read.
277
+
278
+ #### Imported function bodies are not analyzed
279
+
280
+ Freerange does not follow imported functions. Keep a numeric rule that needs analysis in a supported helper with explicit inputs:
281
+
282
+ ```ts
283
+ export function labelWidthFromMeasurement(measuredWidth: number): number {
284
+ return Math.max(120, measuredWidth + 32)
285
+ }
286
+ ```
287
+
288
+ An unsupported caller can pass `measureText(text)` into this helper, allowing Freerange to verify the rule but not the imported measurement. Imported constants work when their initializer resolves to a numeric literal such as `export const GAP = 24`.
289
+
290
+ #### No higher-order function analysis
291
+
292
+ Freerange does not analyze callbacks passed to higher-order functions such as `reduce`, `map`, or `filter`. For a simple scalar aggregation, write the loop directly:
293
+
294
+ ```ts
295
+ export function totalWidth(widths: number[]): number {
296
+ let total = 0
297
+ for (let index = 0; index < widths.length; index += 1) {
298
+ total += widths[index]!
139
299
  }
140
- ```
300
+ return total
301
+ }
302
+ ```
303
+
304
+ This is not a general replacement for `map` or `filter`: object and array writes remain unsupported, and callback arguments, effects, and result allocation may matter. Audit code: `[use-loop-for-aggregation]`.
305
+
306
+ ### Objects, Arrays, and Changing State
307
+
308
+ Freerange reads plain objects, fixed tuples, dense arrays, and tagged unions declared in the project through at most eight nested levels. Give each union case a tag, use an exhaustive non-fallthrough `switch`, and keep deeply nested or unclassifiable data outside important numeric helpers.
309
+
310
+ Freerange assumes that property reads are stable and perform no work during one analyzed synchronous call. A getter or Proxy that changes its answer or performs work is outside the scope.
311
+
312
+ #### No object and array writes
313
+
314
+ Freerange allows local variables to be reassigned but does not track writes through an object or array. Return a new value when the application does not require mutation or stable object identity:
315
+
316
+ ```ts
317
+ export function moveRight(point: {x: number; y: number}, distance: number): {x: number; y: number} {
318
+ return {x: point.x + distance, y: point.y}
319
+ }
320
+ ```
321
+
322
+ Object spread is also unsupported because JavaScript copies only an object's own enumerable properties, which may not match the fields declared by its TypeScript type. List the fields explicitly. Rebuilding an object is not equivalent to mutation when other code observes its identity or the mutation.
323
+
324
+ #### Reads of changing state are not referentially transparent
325
+
326
+ Referential transparency means that evaluating the same expression again is equivalent to reusing its previous result. Freerange does not make that assumption for a clock, viewport, scroll position, or mutable module binding. Store one read when the check and later use should observe the same value:
327
+
328
+ ```ts
329
+ export function viewportScale(): number {
330
+ const viewportWidth = window.innerWidth
331
+ if (viewportWidth === 0) return 0
332
+ return 100 / viewportWidth
333
+ }
334
+ ```
141
335
 
142
- - **Name a calculation before checking it.** If the divisor is `oldMax - oldMin`, write `const oldSpan = oldMax - oldMin`, check `oldSpan === 0`, and divide by `oldSpan`. Checking `oldMin === oldMax` does not tell Freerange about the separately calculated `oldSpan`. Audit code: `[guard-derived-value]`.
336
+ Keep separate reads when two observations are intentional, such as two clock reads used to measure elapsed time.
143
337
 
144
- - **Decide how invalid inputs should be handled before using them.** If `columnCount` must be a positive integer, either require that with leading `console.assert` calls or normalize it with `Math.max(1, Math.floor(columnCount))`. Only normalize when the application actually wants that runtime behavior. Audit code: `[encode-input-rule]`.
338
+ #### Array reads require dense arrays and valid indexes
145
339
 
146
- - **Choose the order of arithmetic deliberately.** Formulas that are equivalent on paper can round, overflow, or underflow differently with JavaScript numbers. For example, `frameWidth / (imageWidth / imageHeight)` introduces a ratio that can round to zero; `(frameWidth * imageHeight) / imageWidth` avoids that particular problem. The two expressions can still round differently. Audit code: `[use-direct-operands]`.
340
+ Use `values[index] ?? fallback` only when the application wants a fallback. Otherwise, prove that `index` is an integer from zero through `values.length - 1` before using `values[index]!`. A bounds check cannot detect a hole in a sparse array, so Freerange expects arrays to be dense. Audit codes: `[handle-missing-element]`, `[guard-array-index]`.
147
341
 
148
- - **Decide what a missing array element means.** Use `values[index] ?? fallback` only when the application really wants a fallback. Otherwise, prove that `index` is an integer from zero through `values.length - 1` before using `values[index]!`. A bounds check cannot detect a hole in a sparse array, so Freerange expects arrays to be dense. Audit codes: `[handle-missing-element]`, `[guard-array-index]`.
342
+ ### Loops
149
343
 
150
- - **Write the condition and loop directly.** Prefer `width === 0` over using a number as a condition, such as `width || 1`. Use a regular loop for a simple dense-array calculation when callback arguments, callback effects, and a newly allocated result array do not matter. Audit codes: `[write-explicit-condition]`, `[use-loop-for-aggregation]`.
344
+ #### Loops find stable ranges, not exact formulas
151
345
 
152
- - **Write object copies explicitly.** Use `{width: layout.width, height: layout.height}` instead of `{...layout}`. Use dense arrays and fixed-length tuples, and do not modify objects or arrays after creating them. Rebuilding an object is not equivalent to mutation when other code observes its identity or the mutation.
346
+ Freerange checks a loop until the possible values at the start of an iteration stop changing. It does not simulate the exact runtime iteration count or derive a formula for the final value:
153
347
 
154
- - **Give each union case a tag and switch on it.** Use a tagged union when different cases carry different fields. Make the `switch` exhaustive and do not use fallthrough.
348
+ ```ts
349
+ export function fixedTotal(): number {
350
+ let total = 0
351
+ for (let index = 0; index < 3; index += 1) {
352
+ total += 2
353
+ }
354
+ return total
355
+ }
356
+ ```
155
357
 
156
- - **Check and use the same local value.** When a value comes from module, class, or reactive state, store it in a local first. For example, write `const currentScale = scale; if (currentScale !== null) return currentScale` instead of checking one read of `scale` and returning another.
358
+ Freerange knows that the result is a nonnegative integer but does not derive that it is exactly `6`. Ordinary counting loops usually settle after two or three checks. If a range still changes after 16 checks, Freerange stops analyzing that path. Write a formula directly when it is the intended implementation, but do not replace repeated floating-point arithmetic with multiplication unless the different rounding behavior is acceptable.
157
359
 
158
- - **Use precise TypeScript types.** Avoid `any`, casts, and suppression comments. Parse external data before passing it to a numeric helper, give the helper typed parameters, and pass only the fields it uses. A file containing `@ts-ignore`, `@ts-expect-error`, `@ts-nocheck`, or `eval` is rejected because its declared types cannot be trusted.
360
+ Code outside this scope may make a result less precise or stop analysis. Freerange does not publish a stronger guarantee by pretending that unsupported code was understood. If an unsupported pattern is important and cannot be reasonably refactored, please file an issue.
159
361
 
160
362
  ## Recommended TypeScript Config
161
363
 
@@ -186,53 +388,17 @@ Freerange uses a few terms consistently:
186
388
  - `partially supported`: Freerange can analyze some, but not all, of the function.
187
389
  - `skipped`: some top-level statements in the modules weren't analyzed.
188
390
 
189
- A caller requirement is not automatically a bug. For example, `requires: columns >= 1` means the function is safe under that condition; it does not mean Freerange found a caller passing zero. Freerange checks supported same-file calls, but it is not a repository-wide call-site verifier. Imported calls and unsupported callers may remain unchecked.
190
-
191
- An `ensures` line assumes its `requires` and `assumes`. A requirement may be a real API rule, or it may expose a relationship Freerange cannot currently prove. An assumption may identify a real input boundary or an analysis limitation. Decide what the program should do before changing code to remove either one.
192
-
193
- Always read the coverage line. No findings does not mean an unsupported file is safe. A derived guarantee becoming weaker, for example `at least 54` becoming `at least 0`, appears in the audit rather than the shorter findings output.
194
-
195
- ## Analysis Limits
196
-
197
- If your production code actually needs support beyond these limits, please file an issue! We're open to relaxing the limits.
198
-
199
- ### Numbers
200
-
201
- Freerange's numeric analysis is designed around arithmetic used in layouts and other everyday application code. For each TypeScript `number`, Freerange tracks one continuous range, whether the value is an integer, whether it may be `NaN` or infinite, and at most one exact number that a branch proved impossible. For example, after `value !== 0`, Freerange can remember that `value` cannot be `0`.
202
-
203
- Freerange does not keep separate ranges or arbitrary sets of possible numbers. If one branch produces `1..2` and another produces `10..11`, Freerange keeps the combined range `1..11`. A later check that rules out a different exact number may replace the number remembered from an earlier check.
391
+ ### Caller Requirements
204
392
 
205
- Freerange recognizes repeated uses of one already-computed value, including aliases, stable property and array reads, lengths, and duplicate arguments passed to a supported function in the same file. For example, `const span = right - left; span - span` is exactly `0` unless `span` is infinite or `NaN`. Two separate evaluations are not assumed equal, even when their source code looks the same; store the result in a local when that identity matters.
393
+ Every plain `number` parameter must be finite and not `NaN`. The same rule applies to numeric fields in fixed-shape object parameters, even when the function does not read them. Numeric literal types such as `1 | 2` already satisfy the rule. Nullable numbers, arrays, tuples, and tagged unions use more specific `assumes` lines instead. A supported literal default can satisfy the requirement when a caller omits an argument.
206
394
 
207
- Earlier versions included an exact rational linear prover based on Farkas' lemma and other relational machinery. The current analyzer does not use that machinery or an SMT solver. In practice, those approaches made analysis unpredictable without proving much more real-world code. We also decided against analyzing numbers as real numbers, which would have been sweet, because doing so produces false proofs: floating-point arithmetic is not associative, rounds results, and can overflow or underflow.
395
+ Division and array reads can create additional requirements. Freerange tries to express them using the function's parameters so that supported same-file callers can prove them, pass them to their own callers, or report a definitely invalid argument. If a condition cannot be expressed that way, `fr --audit` prints a local `assumes` line instead.
208
396
 
209
- ### Function calls
210
-
211
- Freerange follows calls to supported functions in the same file using the ranges known at each call. Imported functions are not followed. Imported constants are followed only when they resolve to a numeric literal such as `export const GAP = 24`. Literal default parameters work in supported calls; object and calculated defaults do not. Passing more arguments than the implementation declares is also unsupported. Unknown calls, when callbacks run, caught exceptions, changes made through another reference to the same object, and most framework behavior remain outside the subset.
212
-
213
- ### Static assertions
214
-
215
- Inside a `console.assert`, Freerange can follow common UI calculations through named values: `Math.min` and `Math.max`, addition or subtraction by a nonnegative value, the same multiplication by a nonnegative value on both sides of a comparison, the fact that `index % columnCount < columnCount` when `columnCount` is positive, and fields read from a freshly constructed record. Freerange does not chain arbitrary comparisons: `left <= middle` and `middle <= right` do not by themselves prove `left <= right`.
216
-
217
- ### Loops
218
-
219
- Freerange analyzes a loop again until the ranges known at the start of an iteration stop changing. Freerange does not simulate every runtime iteration or try to produce a formula for the final value. Ordinary counting loops usually settle after two or three analysis passes. If the ranges still change after 16 passes, analysis stops for that path.
220
-
221
- ### Objects and arrays
222
-
223
- Freerange follows records, tuples, arrays, and tagged unions declared in your project through at most eight nested levels. A deeper property becomes unknown. A function is unsupported when its top-level input type cannot be represented.
224
-
225
- Property reads are assumed to be stable and side-effect-free during one analyzed function call. A getter or Proxy that changes its answer or performs work is outside the model. Property writes, including assignments that invoke setters, are unsupported. Object spread is also unsupported because JavaScript copies only an object's own enumerable properties, which may not match the fields declared by its TypeScript type.
226
-
227
- ### Caller requirements
228
-
229
- Every plain `number` parameter must be finite and not `NaN`. The same rule applies to number fields in fixed-shape object parameters, even when the function does not read them. Numeric literal types such as `1 | 2` already satisfy the rule. Nullable numbers, arrays, tuples, and tagged unions keep their more specific `assumes` lines instead. A supported literal default is used when an argument is omitted, so the default can satisfy the requirement automatically.
230
-
231
- Calls to supported functions in the same file either prove these requirements, pass them outward to their own callers, or report a definitely invalid argument. A successful call also proves that the same stored argument is finite afterward. Writing `console.assert(Number.isFinite(value))` at the start of the function is allowed but normally redundant; `Number.isInteger(value)` is stronger and replaces the finite requirement.
397
+ A caller requirement is not automatically a bug. For example, `requires: columns >= 1` means the function is safe under that condition; it does not mean Freerange found a caller passing zero. Freerange checks supported same-file calls, but it is not a repository-wide call-site verifier. Imported calls and unsupported callers may remain unchecked.
232
398
 
233
- Division and array reads can create additional requirements. Freerange tries to express each one using the function's parameters so callers can be checked. A later operation on the same stored value and path may rely on that requirement; assigning a new value or repeating the calculation starts over. Freerange follows each intermediate calculation at most once. If one pass cannot reach the parameters, Freerange prints a local `assumes` condition instead.
399
+ An `ensures` line assumes its `requires` and `assumes`. A requirement may be a real API rule, or it may expose a relationship Freerange cannot currently prove. An assumption may identify a real input boundary or an analysis limitation. Decide what the program should do before changing code to remove either one.
234
400
 
235
- These limits may make a result less precise or stop analysis, but they cannot make a guarantee stronger than the code supports.
401
+ Always read the coverage line. No findings does not mean an unsupported file is safe. A derived guarantee becoming weaker, for example `at least 54` becoming `at least 0`, appears in the audit rather than the shorter findings output.
236
402
 
237
403
  ## Development
238
404
 
@@ -240,3 +406,7 @@ These limits may make a result less precise or stop analysis, but they cannot ma
240
406
  bun install
241
407
  bun run check
242
408
  ```
409
+
410
+ ## Credits
411
+
412
+ [Infer](https://github.com/facebook/infer), [AlphaProof](https://deepmind.google/blog/ai-solves-imo-problems-at-silver-medal-level/)