feat: make MAX_CALLABLE_VALUE_TARGETS configurable via env (#2725)
* feat(scope-resolution): make MAX_CALLABLE_VALUE_TARGETS configurable via env The branch's original commit was a whole-file snapshot taken at a stale base and never touched the constant, so the env read was missing and the branch's own test failed. Implemented here, matching the sibling GITNEXUS_MAX_PROPERTY_DISPATCH_FANOUT knob. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(callable-value-flow): add env override tests * docs(callable-value-flow): document GITNEXUS_MAX_CALLABLE_VALUE_TARGETS env Adds a Troubleshooting subsection to README.md and a commented entry to gitnexus/.env.example for the new per-callable-site dispatch-target cap (default 32), following the maintainer's review request to document the knob alongside its implementation. --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Ubuntu <ubuntu@localhost.localdomain> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
This commit is contained in:
co-authored by
Gergo Magyar
Claude Opus 5
Ubuntu
Gergő Magyar
parent
bebb1d2367
commit
99291891b7
@@ -38,3 +38,10 @@
|
||||
# Positive integer only — non-integer or < 1 values fall back to 32.
|
||||
# See README § "Scope-resolution property-key dispatch cap".
|
||||
# GITNEXUS_MAX_PROPERTY_DISPATCH_FANOUT=32
|
||||
|
||||
# Per-callable-site dispatch-target cap (default 32). Raise this for repos whose
|
||||
# wide dispatch tables overflow the default and lose a whole call chain; analyze
|
||||
# then logs "callable-value-flow: candidate set exceeded the cap". Positive
|
||||
# integer only — non-integer or < 1 values fall back to 32.
|
||||
# See README § "Scope-resolution dispatch-target cap".
|
||||
# GITNEXUS_MAX_CALLABLE_VALUE_TARGETS=64
|
||||
|
||||
@@ -711,6 +711,33 @@ export GITNEXUS_MAX_PROPERTY_DISPATCH_FANOUT=64
|
||||
npx gitnexus analyze --force
|
||||
```
|
||||
|
||||
### Scope-resolution dispatch-target cap
|
||||
|
||||
During scope resolution GitNexus resolves calls that flow through *callable
|
||||
values* — function/method references bound to variables, passed as arguments,
|
||||
or stored in maps/tables. To keep that inclusion-based resolution finite, each
|
||||
callable site is capped at **32 dispatch targets**. When a site gathers more
|
||||
candidates than the cap it is treated as **overflowed** and *all* of its call
|
||||
edges are dropped — a cliff, not a tail, so a repository with a legitimately
|
||||
wide dispatch table (a single callable site resolving to 33+ targets) loses
|
||||
that site's whole call chain. In that case `analyze` logs
|
||||
`callable-value-flow: candidate set exceeded the cap; no partial CALLS emitted`
|
||||
alongside a warning carrying the language, the overflowing context, the
|
||||
candidate count, and the cap (32).
|
||||
|
||||
Raise the cap for such repositories:
|
||||
|
||||
| Variable | Default | Effect |
|
||||
| ------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `GITNEXUS_MAX_CALLABLE_VALUE_TARGETS` | `32` | Per-callable-site dispatch-target cap in the callable-value-flow scope-resolution pass. Set to a positive integer to raise it for repositories whose wide dispatch tables overflow the default and lose a whole call chain; non-integer or `< 1` values fall back to `32`. Lowering it tightens the overflow budget. |
|
||||
|
||||
```bash
|
||||
# A callable site resolving to 48 targets overflows the default 32 and drops
|
||||
# the chain — raise the cap for that repo and rebuild so scope resolution reruns.
|
||||
export GITNEXUS_MAX_CALLABLE_VALUE_TARGETS=64
|
||||
npx gitnexus analyze --force
|
||||
```
|
||||
|
||||
### Hook augmentation and skip diagnostics
|
||||
|
||||
The Claude Code / Antigravity hooks keep their **stderr** silent on normal skip
|
||||
|
||||
@@ -29,7 +29,17 @@ import { resolveInheritanceBaseInScope } from '../scope/walkers.js';
|
||||
import { definitionIdPosition } from '../utils/definition-id.js';
|
||||
import { narrowOverloadCandidates } from './overload-narrowing.js';
|
||||
|
||||
export const MAX_CALLABLE_VALUE_TARGETS = 32;
|
||||
/**
|
||||
* Per-site dispatch-target cap. Above it the site is treated as overflowed and
|
||||
* its edges are dropped — a cliff, so a repo with a legitimately wide dispatch
|
||||
* table (33+ candidates on one callable site) loses the whole call chain.
|
||||
*
|
||||
* Override via `GITNEXUS_MAX_CALLABLE_VALUE_TARGETS` for such repos.
|
||||
*/
|
||||
export const MAX_CALLABLE_VALUE_TARGETS = (() => {
|
||||
const env = Number(process.env.GITNEXUS_MAX_CALLABLE_VALUE_TARGETS);
|
||||
return Number.isInteger(env) && env >= 1 ? env : 32;
|
||||
})();
|
||||
|
||||
interface Target {
|
||||
readonly id: string;
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { describe, expect, it, afterEach } from 'vitest';
|
||||
|
||||
/**
|
||||
* Verifies that MAX_CALLABLE_VALUE_TARGETS reads the
|
||||
* GITNEXUS_MAX_CALLABLE_VALUE_TARGETS environment variable at module load time
|
||||
* and falls back to the default (32) when unset.
|
||||
*
|
||||
* Because the constant is evaluated once at import time, we use a dynamic
|
||||
* import per test to get a fresh module instance under each env configuration.
|
||||
*/
|
||||
|
||||
const MODULE_PATH = '../../../src/core/ingestion/scope-resolution/passes/callable-value-flow.js';
|
||||
|
||||
async function loadWithEnv(envValue: string | undefined): Promise<number> {
|
||||
// vitest module cache is keyed by path; vi.resetModules is needed for re-import
|
||||
const { vi } = await import('vitest');
|
||||
vi.resetModules();
|
||||
|
||||
if (envValue === undefined) {
|
||||
delete process.env.GITNEXUS_MAX_CALLABLE_VALUE_TARGETS;
|
||||
} else {
|
||||
process.env.GITNEXUS_MAX_CALLABLE_VALUE_TARGETS = envValue;
|
||||
}
|
||||
|
||||
const mod = await import(MODULE_PATH);
|
||||
return mod.MAX_CALLABLE_VALUE_TARGETS;
|
||||
}
|
||||
|
||||
describe('MAX_CALLABLE_VALUE_TARGETS env override', () => {
|
||||
afterEach(() => {
|
||||
delete process.env.GITNEXUS_MAX_CALLABLE_VALUE_TARGETS;
|
||||
});
|
||||
|
||||
it('defaults to 32 when the env var is not set', async () => {
|
||||
const cap = await loadWithEnv(undefined);
|
||||
expect(cap).toBe(32);
|
||||
});
|
||||
|
||||
it('respects a valid positive integer override', async () => {
|
||||
const cap = await loadWithEnv('48');
|
||||
expect(cap).toBe(48);
|
||||
});
|
||||
|
||||
it('falls back to default for non-integer values', async () => {
|
||||
expect(await loadWithEnv('abc')).toBe(32);
|
||||
});
|
||||
|
||||
it('falls back to default for values below 1', async () => {
|
||||
expect(await loadWithEnv('0')).toBe(32);
|
||||
expect(await loadWithEnv('-5')).toBe(32);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user