mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-11-04 07:27:20 +00:00
2293 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
03dc0f1743
|
chore(deps): bump actions/setup-python from 7.0.0 to 7.1.0
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 7.0.0 to 7.1.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](
|
||
|
|
c2d9012b58
|
fix(lbug): guard read queries against runaway native memory (#3696) (#3749)
Some checks are pending
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Gitleaks / gitleaks (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
* fix(lbug): guard read queries against runaway native memory (#3696) A single impact query was reported to allocate ~184 GB of native heap on a 66k-symbol index. That memory is outside the buffer pool and V8, so neither bufferManagerSize nor --max-old-space-size bounds it, and the Node binding cannot interrupt a running query. - Add a process-wide memory guard around every pooled read query. While a query runs it samples RSS every 200 ms and, on macOS, the physical footprint for queries running over 1 s (RSS excludes compressed and swapped pages there). Past the budget it names the running queries on stderr and exits with code 70 instead of letting the host run out of memory. GITNEXUS_QUERY_MAX_RSS_MB sets the limit (0 disables); the default is max(8 GiB, 25% of RAM). - Add GITNEXUS_QUERY_TRACE=1: one stderr line per query with duration, rows and memory, so a reporter can identify the query that blows up. - Cap the impact BFS frontier query at 100k rows per depth level. A capped level stops the walk, marks the result partial, and cannot report a LOW/MEDIUM risk. * fix(ci): initialize shards after fork approval GitHub approval increments the attempt number without running any jobs. Allow initial timing snapshot creation on attempt 2 only when the first attempt required approval and had zero jobs. Keep real retries fail-closed on missing frozen input. * fix(impact): qualify both capped risk scores Apply the same conservative cap policy to risk and riskSharedAxes, retain proven high warnings, and strengthen frontier-limit regression assertions. Correct the memory-budget test description. * fix: skip query phases after deadline --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> |
||
|
|
338dee53ee
|
feat(opencode): ship hooks as auto-loaded global plugin for full support (#3619)
* feat(opencode): ship hooks as auto-loaded global plugin for full support * fix(opencode): honor -e/--regexp as pattern and surface non-ENOENT plugin stat errors * fix(opencode): block shell-metachar patterns in Windows fallback and unlink dangling plugin symlinks * fix(opencode): quote-aware argv tokenization for quoted search phrases * fix(opencode): skip plugin write on literal drift, glob token extraction, segment-anchored mutation check * fix(opencode): parse shell search arguments safely * fix(opencode): check multiline git mutations * fix(opencode): recognize quoted git command boundaries * fix(opencode): skip ambiguous search options * docs(opencode): clarify compound search limits --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> |
||
|
|
13d06d41c4
|
feat(ci): rebalance test shards from recorded timings (#3748)
* feat(ci): rebalance test shards from recorded timings * fix(ci): preserve artifact retries for shard timings Find shard-plan generation by its stable step ID and clarify the native runner cohort count (#3748). Note: pre-existing reference-oracle parser type failures in two test_oracle_assets cases remain outside this change. * fix(ci): keep retry timing snapshots immutable Require the existing snapshot on reruns so a failed download cannot refresh history and change shard assignments. Cover the workflow contract and document recovery when the artifact is unavailable. * docs(ci): document shard timing profile selection * fix(ci): balance cold shard estimates consistently --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> |
||
|
|
51ce19d77a
|
fix(impact): avoid upstream file seeds for private types (#3740)
* fix(impact): do not seed the owning file for a type the index recorded as not exported The #480 seed adds the file that DEFINES a Class or Interface to the walk, so the file's IMPORTS edges stand in for the type's importers. For a type recorded as isExported = false no importer can name it, and the file's importers came back as its direct dependants: a private interface used by one function in its file reported eight route files at depth 1, direct: 8, MEDIUM (#3602, second reproduction). The DEFINES seed query now also returns n.isExported, matched on the target's own label since not every node table has the column, and a row with false is left out of the frontier. true and NULL keep the seed. Refs #3602 * fix(impact): skip the owning-file seed only where a false export verdict is file-private `isExported = false` is a narrower fact than "no other file can name it" in most languages: Java package-private, C# and Kotlin `internal`, Go lowercase, Python and Dart `_name`, a Rust item seen from a child module, a C/C++ `static` in a header. Skipping the #480 file seed on every `false` turned the over-approximation into a false negative for them. The meaning of a stored verdict is a property of the language, so it lives on the provider: `LanguageProvider.notExportedIsFilePrivate`, set on TypeScript, JavaScript, Vue (an ES module is the file), Swift (false only for `private`/`fileprivate`) and Zig (no `pub`; `export fn` is recorded true). The seed resolves the provider from the file's path only for a `false` row and skips the file only when the flag is set. Two regressions in the same test file: a Java package-private class and a Kotlin `internal` class, both recorded `false`, keep their importers as direct dependants. Refs #3602 * fix(impact): require an ES export in the file before trusting a false verdict The stored verdict is the provider's export checker, which for an ECMAScript file answers "under an `export` statement?" whatever the module kind: a CommonJS class (`module.exports = Foo`) and a top-level declaration of a global script are both recorded `false`, and both can be named from other files. Trusting the provider flag alone skipped the owning-file seed for them. The index does not record the module kind, but it records the checker's `true`, which only ES `export` syntax produces. The skip now also requires a `true` declaration in the same file (Function, Class or Interface: the tables that carry the column and that a File DEFINES). A CommonJS file, a script, or a module whose exports all live in `Const`/`TypeAlias` keeps the seed, as before this fix. The Java and Kotlin cases now declare a public sibling class, so only the provider verdict keeps their seed; two new cases cover a CommonJS module and a global script. Refs #3602 * fix(impact): preserve uncertain type dependencies * fix(impact): retain exported type alias dependencies --------- Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> |
||
|
|
70e226f9c6
|
chore(deps): consolidate Dependabot updates (#3742)
Combine the six open Dependabot update snapshots across npm and uv. Preserve current main lockfile entries and deduplicate the Babel updates. Related: #3718, #3731, #3732, #3733, #3734, #3735 Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
42d97802ea
|
feat(logger): let a host route GitNexus records into its own sink (#3729)
Some checks are pending
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Gitleaks / gitleaks (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
* feat(logger): let a host route GitNexus records into its own sink A process that embeds GitNexus had no supported way to send the logger's records into its own logging: the singleton and every named child write to stderr, and the only redirect was the test-only capture (#3620). configureLoggerSink({ destination, level, mixin }) routes the default logger and every later createLogger() child to the host's destination, with the mixin's fields on each record, and returns a function that puts the previous state back. flushLoggerSync() flushes a sink destination that has flushSync. Without a call nothing changes: the default path gains one branch at logger construction and none per record, no pretty transport or exit hook is installed for a host sink, and a test capture keeps precedence while it is active. * fix(logger): retire a sink on restore so an out-of-order restore cannot revive it A restore that found its sink displaced by a later one returned without a trace, and the later sink's restore then handed the state back to the sink whose owner had already restored it (review on #3729). A restore now retires its sink for good: when active, the nearest earlier sink that is still live becomes active again, else stderr; when displaced, the active state is left alone but the sink can no longer come back. * test(logger): cover a warn-level host sink and a restore across two retired owners A sink that already enables debug cannot tell whether a child honoured debugEnvVar or only inherited the sink level: dropping debugRequested from the host-sink branch left all 32 tests green. The warn-level case drops an ordinary child's debug record and keeps the debugEnvVar child's. The three-sink case walked past a single retired owner, so a one-hop lookup in nearestLiveSink also passed. Four sinks, with both middle owners retired before the newest restores, reach the oldest live sink only through the loop. --------- Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> |
||
|
|
bad2422aa1
|
feat: add the test scenario map and a Vitest 5 mutation runner (#3686)
* feat(ci): add advisory duplicate-test scan to the PR report Adds `npm run test:dupes` (gitnexus/scripts/test-dupes.ts) and a comment-only CI job that reports, for the lines a PR changes: - same-test: same body and same setup as an existing test - assertions-pinned: every assertion is a literal fn(...) -> matcher that another test already makes - same-fixture-setup: a before* hook builds a fixture another describe already builds - copied-helper: a top-level test helper identical to one elsewhere - orphan-bench-gate: a bench script with --check that nothing runs A case's identity includes its hooks, describe-level statements, wrapper arguments, loop heads, each tables, referenced file-local declarations and resolved imports, with fixture paths compared by value. Extra context can only split a group, so identical bodies that run against different fixtures, mocks or env are not reported. The ci.yml job is continue-on-error and outside the CI Gate. ci-report.yml renders its JSON into the sticky CI comment, keeping only whitelisted kinds and strict path/line shapes because the artifact comes from PR code. `// dupe-ok: <reason>` silences an intentional repeat. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(mutation): record Vitest 5 and Stryker 10 probe findings Go/no-go probe for the Stryker Vitest 5 runner: name-filter separator, per-spec task ids, static mutant activation, TypeScript plugin loading in child processes, setup-file meta and hook ordering. * feat(mutation): prepare a mutable copy and a Vitest mutation config Prepared copy blanks TypeScript as-assertions (Stryker's instrumenter skips every TSAsExpression) with equal-length spaces, checked by AST equality, and relocates gitnexus-shared/src inside the package so it can be mutated. The mutation Vitest config resolves gitnexus-shared to the relocated sources and takes its test list from a scope file. * feat(mutation): add a Stryker test runner for Vitest 5 Stryker's vitest-runner 10.0.0 runs zero tests per mutant under Vitest 5 because Vitest 5 matches testNamePattern against ' > '-joined names. This runner selects tests by Vitest task id per file, keeps the configured fork pool, shares the dry-run manifest across Stryker worker processes, attributes hook-time hits to the suite's tests, and records a per-test outcome class in sidecar files. Adds exact devDependencies @stryker-mutator/core and @stryker-mutator/api 10.0.0. * feat(mutation): add global mutant identities and a span-map check A global mutant identity (original repo path, original content hash, span, mutator, replacement) makes mutants comparable across Stryker runs. The span-map check proves every mutant of a prepared copy sits on the same span and text in the original source, apart from blanked as-assertions. * feat(mutation): add a scope driver for Stryker runs run-scope turns a scope file into a prepared copy, a generated Stryker config (vitest5 or command runner), a Stryker run and normalised results: global mutant ids, runner test ids, sidecar outcome classes, consistency flags and the span-map check. * fix(mutation): count assertion kills only for killed runs A run classified timeout, inconclusive, invalid or infrastructure can still carry assertion rows (a test failing after a swallowed hit-limit error, or under a retry). Those kills are not trustworthy, so assertionKilledBy is now empty unless the run's status is killed. Found by the conformance suite. * feat(mutation): add the runner conformance suite (gate 2) A fixture project under test/fixtures/mutation-conformance pins 23 runner behaviours (static and collection-time mutants, beforeAll attribution, disableBail, it.each row ids, outcome classes for runtime, hook, collection, timeout, infrastructure, retry and zero-test runs, NoCoverage, environment isolation, runner reuse and manifest sharing) in two scheduling profiles. The path map and scope driver accept an explicit gitnexus/test/fixtures/mutation-* root for it. * feat(mutation): classify pure-JS unit tests for mutation runs Walks each unit test's import closure (static imports plus statically resolvable dynamic imports, require and createRequire) and rejects tests that reach native packages, worker threads or child processes, read source or fixture files a prepared copy would break, or contain unresolvable dynamic imports. 247 of 911 unit test files are pure JS. * feat(mutation): add gate 1 execution-path fidelity and the envelope record Gate 1 compares a normal Vitest 5 run of the pure-JS category with the runner's unmutated Stryker run: test identities with locations, outcomes, effective config against an allowed-difference list, resolved module paths (everything inside the active sandbox) and a runtime load check for native bindings, worker construction and child processes. It passes on 246 files / 4541 tests and writes test/fixtures/mutation-oracle/envelope.json. The runtime check found commander loading child_process, so the classifier now rejects tests importing commander. * feat(mutation): add the oracle's command-runner run and classifier The Vitest 5 cross-check oracle runs its own Stryker run with the command runner (cleanTempDir false). A wrapper runs the scope's tests per mutant with an oracle reporter and writes a per-mutant record, and the oracle classifies every mutant into the KTD17 outcome classes with its own classifier. Oracle code imports no runner code. * feat(mutation): add the oracle single-test launcher and cost benchmark launch-single runs one test with one mutant in a fresh process inside the oracle's kept sandbox, selected by file:line plus an anchored name pattern, with the mutant installed before any import, an oracle setup file that proves the installed id and buckets hits (load and collection, hook, per-test), a wall-clock limit and a process-group kill. A launch is valid only when exactly the intended test ran. The benchmark projects the three-scope oracle sample's cost on 4 vCPU. A boundary test keeps oracle code from importing runner code. * feat(mutation): add the oracle freeze and gate 3 compare freeze samples mutants per scope with a recorded seed, tops up strata, counts stratum populations from the oracle's own coverage probe, launches every sampled mutant against every scope test (resumable), and writes a manifest keyed by content hashes of the scope's import closure, Vitest configs and prepare tool. compare checks freshness, inventories (tests and global mutant ids) before outcomes, every mutant's class and sampled per-test outcomes, allowing only the four KTD13 normalisations. * feat(mutation): add gate 4 positive controls and determinism Nine planted controls, selected from the scopes' own Stryker mutants by global id (three in relocated gitnexus-shared as-const tables), must be killed by exactly their expected killers. Two scheduling profiles (concurrency 1 with unlimited runner reuse, concurrency 3 with one mutant per runner process) must give identical outcome classes and kill sets. * ci(mutation): add an advisory mutation-runner validation workflow Runs the runner conformance suite (gate 2) on pull requests that touch the mutation tooling, its fixtures, the gitnexus manifest, lockfile or Vitest config. workflow_dispatch also runs gate 1, the gate 3 oracle compare (or a per-scope re-freeze whose manifests are uploaded, never committed), gate 4 and the oracle cost benchmark. All jobs are advisory. Documents the tooling in TESTING.md and the residual deltas against upstream StrykerJS. * test(mutation): freeze the gate 3 oracle manifests and record Phase 0 results Frozen Vitest 5 cross-check oracle manifests for the three pure-JS scopes (seed u7-gate3-1): shared.ts, schema-constants.ts and middleware.ts. Gate 3 passes: all 1,606 mutant classes and all 20,455 sampled per-test outcomes agree with the runner, 0 differences. Adds the approved Test Scenario Map design (updated with Phase 0 findings and gate results) and the Phase 0 implementation plan. * refactor(mutation): simplify the Phase 0 tooling Shared CLI/fs helpers for the non-oracle tools, an oracle-local util for isObject and atomic writes, formatMutateEntry and formatDuration reused, a typed isUnusableClass, and hot-path fixes: per-sidecar index in the gate 3 compare, lazy suite-chain in the runner's dry-run hit attribution, one Classifier per freeze, memoised display paths and Vitest lookups. Behaviour unchanged: unit tests, gate 3 compare (byte-identical report) and the conformance suite (23/23) pass. * fix(review): apply review findings - Move the duplicate-test scanner out of the published scripts/ directory to gitnexus/tools/test-dupes/ so it no longer ships in the npm package. - Scope the orphan bench gate to touched lines, like every other scanner finding. - Let the conformance environment case reach Stryker through an explicit childEnv override, so it exercises the runner's own __STRYKER_ACTIVE_MUTANT__ deletion (a negative check now fails it). - Do not raise the zero-tests flag when a timeout outside-test row explains the zero. - Run Stryker in its own process group and signal the group on cancellation. - Fingerprint conformance inputs and refuse stale artefacts in --compare-only. - Path-gate the mutation workflow on gitnexus-shared/package.json and the setup action; anchor the PR-report path filter with \A...\z. * chore(security): mark the runner's provide/inject key as a gitleaks false positive gitleaks' generic-api-key rule matched PROTOCOL_KEY = 'gitnexusStrykerVitest5' (a Vitest provide/inject key name). Allow the line inline and ignore the fingerprint in the commit that introduced it. * test(mutation): re-freeze the schema-constants oracle manifest Merging main changed the scope's import closure (template-arguments.ts), so gate 3 reported re-freeze required. Re-frozen with seed u7-gate3-2: 72 sampled mutants, 3024 valid observations, 0 invalid. Gate 3 now passes on all three scopes (shared 1232, schema-constants 72, middleware 302 mutants) with 0 differences. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(plans): add the Test Scenario Map Phase 1 plan * chore(security): stop the gitleaks allowlist comment from quoting the key The previous comment quoted the provide/inject key assignment and gitleaks flagged the comment line itself (generic-api-key). Reword it and allowlist that historical finding by fingerprint. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(mutation): select single tests in the vitest5 runner and keep per-test classes Phase 1 U1 (KTD2, KTD9, KTD16): - driver scope accepts optional testIds, passed as the vitest5.testIds runner option; the dry run collects, maps ids to task ids, runs only those tests and fails before any mutant run on unknown ids - the dry-run sidecar (schema 2) carries the hook marker: per mutant, the tests a suite hook attributed it to - results.json (schema 2) keeps every sidecar attempt's per-test rows (perTest), attemptsDisagree and hookAttributedTests - gate 2 gains a selection scope and hook-marker cases (26 cases hold) Gate 3 hashed inputs are untouched. * refactor(test-dupes): export Layer 0's case extraction for the scenario map Phase 1 U2 (KTD6): export extract, extractCases, buildFrame, closure, staticValue, suppressed and dupeOkReason, plus a per-case record with the describe path, title, context hash, body, each-table node and enclosing loop head. Layer 0's --all scan output is byte-identical before and after (1464 files, 114 findings). * test(mutation): refresh the gate 1 envelope after the runner extensions Gate 1 re-ran over the current pure-JS category after U1 and passed with 0 differences: 251 files, 4667 tests. The classifier now rejects conformance-compare.test.ts (imports the child_process-spawning conformance runner) and php-namespace-extraction.test.ts (now reaches tree-sitter through main), and accepts the three oracle unit tests. * feat(scenario-map): add the eligible-test inventory, identity join and anchors Phase 1 U3 (R1, R2, R15; KTD3, KTD7): - eligibility = gate 1 envelope intersect a fresh classifier run, with the delta; controls mode enrols a fixture's *.case.ts files - static expansion of it.each/test.each/test.for tables and literal for-of loops into per-row instances; anything not statically evaluable is unresolved (fail closed) - a pure join of static instances to runtime records through Phase 0 test ids; any count or name mismatch makes the tests unsupported - continuity anchors (no line; row and body fingerprints) and the one production-file predicate shared by every later stage - test-dupes: additive exports (shapeOf, roleOf, eachTableOf, ASTs, an isTest option); Layer 0 output byte-identical Over the repo: 4667 runtime tests, 4597 joined one-to-one, 70 unsupported (5 tables built at runtime), 0 mismatches. * test(scenario-map): add the controls fixture project with expected verdicts Phase 1 U17: one pair per design §8 pure-JS adversarial case (Math.round, parseInt radix, localeCompare, normalize, Date boundary, regex alternatives, numeric boundary with a constants-only module, an expected-only difference, a derived value reaching toFixed, an enum-member-only difference), an order-dependent pair, an object-key row, and positive controls (verbatim duplicate, repo arithmetic, Set table reads). expected.json was written from the design's stated outcomes. The fixture has its own Vitest project and tsconfig; no repo project collects it. * feat(scenario-map): extract per-instance checks and context Phase 1 U5 (R5, KTD6): - checks: the shape of each expect(...).matcher(...) with literals abstracted and selector literals in the subject kept exact; snapshots hashed from inline text or the .snap entry; a documented supported matcher list, anything else unsupported; per-row for it.each/test.for - context: Layer 0's context hash plus body-level mocks/env writes and the test call's own modifiers and options - checksContextKey for the second candidate source Over the 251 eligible files: 4600 instances in 2.9 s, 183 with an unsupported form, 37 with an unresolved selector. * feat(scenario-map): add Layer 1 per-test V8 coverage and candidate retrieval Phase 1 U4 (R3, R4; KTD4, KTD5): - a Vitest setup file records per-test V8 block coverage in a prepared copy under the mutation config (gitnexus-shared runs from source), with design §5.2 attribution: module load discarded, a suite's beforeAll delta taken at its first beforeEach, concurrent tests flagged unattributable - provenance per test: executed files, native-package entries and worker or child-process spawn call sites - candidates: exact Jaccard >= 0.99 (size-ratio and prefix filters) plus identical checks-and-context buckets; connected components schedule Layer 3 only Over the 251 eligible files: 4667 tests in 1:55 (82.8 s Vitest), 6281 candidate pairs in 611 groups, largest group 36 tests. Controls: 16/16 pairs are candidates, 0 cross-pair candidates. The directory is path-coverage/ because coverage/ is ignored by git, prettier and eslint. * feat(scenario-map): compute the per-instance input facet Phase 1 U6 (R5, R9; KTD6), porting the input-facet spike without its hard-coded inputs: - rules 1-8: tree-sitter shapes of inline source, the COBOL profile, best-fit production tables (executed path, 2-hop import closure, property columns; never pooled), selector literals (reusing the checks predicate), object keys, source order, shared fixtures, fixture files - numeric literals with 0/1/-1/negatives exact, and the +-1 rule against production numeric constants from executed files and the static import closure (folded initialisers included) - discarded values per position with source locations for value flow - the residual-difference guard: a body difference the facet misses makes the pair facet-unequal and both instances unresolved Controls: all 16 pairs match expected.json. The spike's untouched holdout-4 still scores 23/23 distinct. Eligible run: 4667 instances in about 33 s, 245 unresolved. * feat(scenario-map): run Layer 3 per candidate group and merge raw observations Phase 1 U7 (R6, R7, R11; KTD7, KTD8, KTD14, KTD17): - whole-file mutate scope per group from Layer 1 provenance and the static closure; executed-but-unmutated repo files mark tests excluded - a run manifest of the prepared inputs that every shard verifies - two plain Vitest 5 baselines (fresh instance per run) plus the Stryker dry run must agree; the generated config must carry perTest coverage, disableBail and static mutants before mutating - raw observations from every sidecar attempt, keyed by group, run kind, lineage, attempt, test and mutant; class disagreement is inconclusive and a missing result is unknown, never a non-kill - deterministic shard planning that never splits a group, with group-over-budget and start-only-if-it-fits rules Controls: all 16 pairs ran as real groups; every kill set matches expected.json. * feat(scenario-map): add the full isolation pass for repeat-stage pairs Phase 1 U8 (R7; KTD8, KTD9): - select repeat-stage pairs (same coveredBy path, checks, context, facet without a residual difference, and the same non-empty kill set) - per pair one together run and one alone run per test (shared across pairs), mutate restricted to the covered universe, a fresh runner process per mutant, all behind the same KTD8 baseline and config checks (runGroup's steps moved into runMutationRun) - coverage and outcome class per test and mutant must agree across main, together and alone; a missing rerun result is unknown; hook-attributed mutants cap the pair at weak Controls: order-dependent ends disagrees; verbatim-duplicate, repo-arithmetic and set-non-member agree, matching expected.json. Measured fresh-process cost: about 5.5 s per mutant. * feat(scenario-map): add the row-deletion operator Phase 1 U9 (R8, KTD10): - find the one supported production table (object literal with plain keys, literal Set/Map) holding both differing values; spreads, computed keys, Object.assign, writes, getters, proxies, duplicate keys or an ambiguous table make the outcome inconclusive - splice one key's text in a throwaway prepared copy, run the pair with a fresh Vitest instance per run after a control run that proves both pass and the edited file loaded - symmetric / asymmetric (Row) / not applicable / inconclusive, with RowDeletion global mutant ids - vitest-run: an opt-in load probe (probeFiles, loadedFiles) Controls: all 16 pairs match expected.json (object-key-row and set-membership asymmetric). * feat(scenario-map): add the static value-flow check Phase 1 U10 (R9, KTD11): for facet-equal pairs whose discarded values differ, all sources must come back clean or the pair is vetoed to Review: - Babel binds each differing value to its entry (call argument, fixture field, table key); an expected-only difference is unknown - the TypeScript 7.0.2 unstable checker resolves entries and callees through aliases; a version mismatch degrades the whole run - a walk stitched across functions over GitNexus PDG REACHING_DEF and CALL_SUMMARY edges (pdg_query flows are intra-procedural): opaque sinks (lib/@types/node_modules, any regex) veto, truncation, dynamic dispatch, a depth cap or a missing PDG layer are unknown or degraded; repo code is clean only inside the pair's mutated files - row-deletion exports scanSupportedTables so table reads mean exactly the tables row deletion supports Controls: 0 mismatches against expected.json. Whole-repo analyze --pdg: 6:49 wall, 8.4 GB peak RSS. * feat(scenario-map): add the value-flow trace run Phase 1 U11 (R9, KTD11): one traced Vitest 5 run over the tests of every facet-equal candidate pair, with a setup file that wraps value-sensitive built-ins (Math, string transforms and comparisons, RegExp, number parsing and formatting, JSON, Intl, Date, sort, plus U10's opaque callees) and records, per test and production call site, the receiver, arguments and count. Storage and table reads stay unwrapped. A shared site with a different receiver or argument, or asymmetric sites or counts, vetoes the pair; a test whose outcome changes under tracing makes it inconclusive. Controls: all 13 pairs with a trace expectation match expected.json, including receivers 24 and 25 reaching toFixed. Overhead on 40 eligible files: 1.19x pooled, no outcome changes. * feat(scenario-map): add the verdict engine Phase 1 U12 (R10, R11, R15; KTD5, KTD8, KTD9, KTD12): a pure function over merged observations and every stage's evidence. Design §2 precedence as an ordered rule list (Inconclusive, Out of scope, Weak, Repeat strong, Repeat weak, Row, Review, Distinct), with the value-flow and trace vetoes as a gate before any repeat category and the residual difference ending in Review. One verdict per pair with its deciding rule and every weak disqualifier; per-test Weak, Inconclusive and Subsumed; per-file gaps recomputed from all observations with the §5.4 wording; dupe-ok pairs kept with their evidence. Output is deterministic. Controls: all 16 verdicts and rules match expected.json. * feat(scenario-map): build the report, issue body and week-over-week diff Phase 1 U13 (R12, R13; KTD13, KTD15): - the JSON report: run provenance (the design §7 cache-key inputs), completeness and degraded flags, the eligibility delta, Layer 0 findings, every verdict with its evidence, per-test properties, gaps, a seeded spot audit of 10 strong candidates, N of M evaluated and the precision-not-measured statement - continuity: mutant fingerprints without revision or span, universe ids from the pair's common universe, evidence hashes per design §5.5 - a markdown rendering and the tracking-issue body (strong repeats, weak tests, gaps; truncated at 60,000 characters; a run-id marker so a stale run never overwrites a newer body) - baseline comparison: new, changed, unchanged, resolved, with first-run and incompatible-baseline banners * feat(scenario-map): add the CLI and the end-to-end controls run Phase 1 U14 (R14, R16; KTD1, KTD14, KTD17): npm run test:scenario-map with plan, shard, value-flow, merge, all and controls stages exchanging JSON in a work directory, so CI jobs and a local run produce the same report. Shards batch groups into one mutation run when their combined estimate fits, and persist row-deletion and isolation results as they finish. Exit codes are set explicitly. controls: all 16 fixture pairs match expected.json (exit 0, 76 min). * feat(scenario-map): fit the scheduled run into the CI budget Phase 1 U14 follow-up (KTD14, KTD17), measured on a seeded slice of the real category; no evidence rule is relaxed: - value flow runs before the shards and re-plans them, so isolation is spent only on pairs that can still be strong (a value-flow veto or unknown ends Review, a trace inconclusive ends Inconclusive); a missing, stale or degraded value-flow result falls back to the provisional plan - a shards stage prints the final shard count for the CI matrix - --concurrency and --speed-factor feed the estimate; the main-run cost form is fixed + mutated bytes (measured; it does not grow with tests) - a group whose isolation does not fit still runs its main run and row deletion; its pairs get isolation not-run (weak cap), and a shard isolates deferred pairs only when their measured universe fits - groups that execute no production file are not run; their pairs are out of scope Predicted CI run: 9 shards at concurrency 4, 1658-1829 min in total, largest shard 196-253 min (budget 300), 0 groups over budget. controls: 16/16 pairs match, 28 min. * ci(scenario-map): add the weekly Test Scenario Map workflow and a PR controls job Phase 1 U15 (R13, R14; KTD13, KTD14, KTD17): - test-scenario-map.yml (weekly + dispatch, advisory): plan, value-flow, shards (sizes the matrix from the value-flow re-plan), a shard matrix (fail-fast off, max-parallel 10, 300-minute deadline), merge (always, against the newest scenario-map-baseline artifact) and upsert-issue (default branch only; the only job with issues: write; replaces the tracking issue's body only for a complete run that is not older than the body's run marker, comments on partial runs, never closes it) - mutation-runner-validation.yml: a path-gated scenario-map-controls job that runs the controls fixture end to end on PRs touching the tooling actionlint (with shellcheck), zizmor 1.24.1 and the concurrency check pass. Neither workflow can run before the merge. * docs(scenario-map): record Phase 1 in the design and the testing guide Phase 1 U16: design corrections (the stitched PDG walk, the trace's receivers and scope, row deletion and baselines on plain Vitest 5, the isolation pass on testIds after value flow), the demote-only additions, the refreshed gates, the measured Phase 1 costs and the CI topology change; TESTING.md gains a Test Scenario Map section (stages, controls fixture, supported matchers, wrapped built-ins, weekly workflow, dupe-ok). * fix(scenario-map): store observations compactly so large batches fit in memory The first full run crashed a shard with "Invalid string length": observations were materialised for every collected test x every mutant, including a valid not-covered survival for each pair (1.6M objects for a batch of 73 selected tests over 21,965 mutants; the batch file needed about 700 MB of JSON). - only a run's selected tests produce observations - each run records its universe (selected tests, mutants with their status, reasons and hook marker); only rows, covered or static mutants without a row, and hook-named tests are stored explicitly; a not-covered survival is derived when read - every consumer (pair comparison, isolation, verdict engine, report, batch split) reads through one index; group observation and isolation run files move to schema 2, and old files are rejected with a message - merge lists an unreadable group result as incomplete instead of dropping it; repeat-stage decisions keep comparison summaries only On the crashing batch: 141,308 explicit observations instead of 1.6M, 12.1 MB instead of an overflow, and a byte-identical verdict report. controls: 16/16 pairs match. * docs(scenario-map): record the Phase 1 measured slice; give the real-repo classify test headroom - design §9: the full-category report comes from the first weekly workflow run; the local full run was stopped because it saturated the shared machine. Records the measured slice and the whole-category plan and value-flow figures. - classify.test.ts: classify the real repo once in beforeAll with a 120 s timeout; it takes about 30 s on a busy host and timed out at the default 30 s. * fix(review): apply review findings and simplify the scenario-map tool Review (ce-code-review, validated findings): - trace: a baseline or traced run that is invalid, inconclusive, retried or has outside-test failures, unhandled errors, unwrapped entries or recorder errors makes every pair inconclusive (trace-run-invalid), never clean; the value-flow stage reports it and merge marks the run partial - verdict: tests pin that a not-required static check for differing values reads as unknown, plus the worker-spawn, missing-provenance, unresolved-input and unknown-hook-marker weak caps - workflow: the baseline comes only from this workflow's own scheduled or dispatched default-branch runs of this repository; an unreadable baseline shows the incompatible-baseline banner instead of failing merge - controls: the oracle fails on an empty expected list, on any unlisted repeat verdict and on a case file without an expected pair - value flow: the PDG analyze budget (25 min) sits below the 50-minute CI step; the TypeScript checker server is closed when opening fails - --concurrency now also bounds every in-process Vitest run (maxWorkers), so local runs stay within the requested cores - merge reads isolation runs tolerantly (an unreadable file makes the run partial); a duplicate test title is renamed; a stale TESTING.md heading reference is updated Simplification (behavior-preserving; merge and plan outputs byte-identical on fixed inputs): one util.ts for shared helpers, a run-level row-deletion cache, one classify() per plan, memoised universe ids and single-pass pair/subsumption/report loops. * fix(test-dupes): keep beforeAll and beforeEach fixture builds apart A same-fixture-setup key now includes the hook name. A beforeAll fixture is built once per suite and a beforeEach one per test, so sharing one result between them would drop per-test isolation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ci(scenario-map): temporary label-gated pull_request run for PR #3686 schedule and workflow_dispatch only run from the default branch, so the Test Scenario Map workflow cannot run before this PR merges. Adding the run-ci label to the PR starts one full run. A pull_request run writes no tracking issue and no baseline. Remove the trigger before merge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ci(scenario-map): remove the temporary pull_request trigger The one full run on PR #3686 finished (run 40183982111, complete, 9 of 9 shards). This reverts |
||
|
|
b2c72997ea
|
fix(scope): reduce memory for terminal import misses (#3741)
* fix(scope): reduce memory for terminal import misses * fix(review): cover external imports and cache isolation --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> |
||
|
|
374b10de86
|
fix(mcp): shut down when the stdio parent exits (#3737)
* fix(mcp): shut down when the stdio parent exits * test(mcp): simplify parent-exit stream tracking --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> |
||
|
|
3a29876365
|
fix(mcp): reduce tool context and preserve repository hints (#3739)
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> |
||
|
|
9a0ef8fbbb
|
fix(eval): reuse reference settings for PR cost previews (#3738)
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> |
||
|
|
afea705e36
|
test(mcp): retain paired performance measurements (#3730)
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> |
||
|
|
c417ccfc81
|
perf(ingestion): reduce indexing overhead and declaration memory (#3726)
Some checks are pending
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Gitleaks / gitleaks (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
* refactor(ingestion): share callable declaration facts Reuse pure parameter and signature facts between structure and scope extraction, with tree-owned caches and read-only parameter contracts. Preserve provider-specific wrappers and consumer projections. Add characterization tests, real-worker parity checks, and reproducible performance measurements. Keep the overall indexing effect explicit. Refs #3723 * fix(bench): create result files exclusively Address CodeQL review feedback on #3726. Use a private default directory and an exclusive file handle so existing files and symlinks cannot be overwritten. * perf: speed analyzer identity guard probes * perf(ingestion): reuse callable type and overload facts * ci: retain callable time and memory benchmarks * chore: use established CI performance measurements * fix(bench): check callable baseline in CI * perf(ingestion): guard callable scaling and reuse export checks * fix(ci): budget for callable scaling matrix * refactor(ingestion): simplify callable handling --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> |
||
|
|
6122657afc
|
fix(mcp): bound query previews and advertise repo defaults (#3728)
* fix(mcp): reduce query payload and retry overhead * fix(mcp): fetch bounded previews at query time --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> |
||
|
|
63f2f8e590
|
fix(ingestion): retain named class-expression owners (#3725)
* fix(ingestion): retain class-expression owners Keep named JS/TS expressions, members and scope declarations on matching graph identities. Distinguish expressions that reuse a local class name. Avoid root enumeration when no file-scope types are configured. 1088 focused tests pass. Full suite: 28407 passed, one pre-existing SSH-signing-sensitive golden failure, 91 skipped. * fix(ingestion): preserve type-alias owners Keep method-local type-alias member edges on the existing simple TypeAlias IDs when JS/TS class qualification is enabled. Otherwise native COPY drops both method and property relationships. Cover TS, TSX and Vue workers plus direct native-persistence reads. Refs #3725 * fix(bench): align JS/TS capture baselines Record the fingerprints introduced by qualified class and member identities. Same-corpus comparisons attribute every changed byte to the new qualified-name tags; removing those tags restores both previous hashes. Keep capture counts and scaling budgets unchanged. --------- Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> |
||
|
|
34b873696b
|
fix(cli): recover pnpm and Bun invocation paths (#3721)
Some checks are pending
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Gitleaks / gitleaks (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
* fix(hooks): skip a pnpm shim that cannot run when picking the runner Fixes #3717 * test(hooks): isolate runner liveness checks Inject pnpm liveness and bunx presence in resolver unit tests so they do not depend on installed package managers. Use fixture-only PATHs and POSIX/Windows launchers to cover failed pnpm probes, banner output, fallback execution, argument forwarding, and child exit codes. * fix(cli): prepare native binary before identity Restore skipped LadybugDB install copies before capturing analyzer identity, while keeping native probes and analyzer imports after it. Exercise real npm, pnpm and Bun tarball installs on Linux, macOS and Windows so runner selection is backed by successful indexing. --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> |
||
|
|
f759753794
|
fix(eval): retain large session measurements (#3722)
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> |
||
|
|
9772bcd24f
|
feat(api_impact): list the callers of the requesting function as indirect consumers (#3720)
* feat(api_impact): list the callers of the requesting function as indirect consumers The FETCHES edge is file-level, so api_impact saw only the file that sends the request. The files that call its request function never mention the URL and were missing from the report (#3644). The edge's reason now names the callables whose spans contain the request sites, one `|via:<id>` field each, under the containment rule the R3-6 sink set already uses; that rule moves into EnclosingCallableIndex so both stay one. Several sites of one file for one route now share the edge instead of colliding on its id. api_impact follows the CALLS edges into those callables one level up and reports the callers as `indirectConsumers`, apart from the direct consumers and outside the risk level. Direct consumer files and the handler file are left out. An index built before the attribution carries no field. Expo navigation sites carry their real line so they attribute to the component that contains them. Closes #3644 * fix(api_impact): attribute a request site by its AST enclosing callable A helper defined on the line of the request has the smaller span, so the line-only lookup named it and the callers of the real requesting function vanished from indirectConsumers. The parser now records the enclosing callable of each fetch site and of each wrapper call (through the call's position), and attribution prefers that identity; the span lookup remains for sites found by text scan. The same preference applies to the R3-6 sink set. A real analyze of the fixture followed by api_impact pins the helper case and the wrapper case. * test(bench): rebaseline receiver resolution for the indirect-consumers fixture The api-impact-indirect-consumers fixture adds three receiver-unresolved sites to the corpus: two `.then(...)` calls on the untyped return of `fetch(...)` and of the local wrapper `apiFetch(...)`, and one `req.query.path` read inside an inline Express handler with an untyped `req`. Call drops 138 -> 140, reads 30 -> 31, all kinds 168 -> 171. No shape-arm state and no existing site changed. * fix(api_impact): preserve module-scope request attribution Keep AST-confirmed module scope distinct from missing attribution for direct fetches, HTTP clients, and wrapper calls. Prevent overlapping callable spans from inventing indirect consumers or process sinks. Preserve span fallback for callable forms the generic identity walk cannot name. Add eight regression cases, including real analyze/api_impact coverage. Rebaseline receiver-resolution counts for the two then calls and one property read added by this PR's existing fixtures; shape expectations are unchanged. --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> |
||
|
|
ee7eff97f7
|
fix(ci): rebalance native tests and stabilize flaky gates (#3719)
* fix(ci): stabilize timing gates and artifact uploads * fix(ci): rebalance undercharged native test shards --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> |
||
|
|
be32540b6b
|
fix(wiki): keep valid Mermaid labels and link text through the sanitizer (#3707)
* fix(wiki): keep valid Mermaid labels and link text through the sanitizer The wiki's Mermaid sanitizer turned some valid diagrams into invalid ones (#3705). `sanitizeMermaidLine` split each line on `EDGE_RE`, whose `\.\.+` alternative matched any run of dots, so the `...` inside a quoted label such as `A["value.when(...)"]` cut the label in two and the remainder was aliased as a node. The text of a `-- text -->` link was split off the same way and, when it held a dot, rewritten as a node reference: `N -- ref.invalidate --> A` became `N -- ref_invalidate["ref.invalidate"] --> A`. Both outputs fail `mermaid.parse`. Since the sanitizer runs again every time the viewer is rebuilt, a diagram fixed by hand broke again. Two changes: - The inside of every quoted string and every node shape (`[...]`, `(...)`, `{...}`) is masked with a placeholder before the line is split and unmasked after, so label text can never read as a link or a node. The delimiters stay, so the node sanitizer still sees where an inline label starts. - `EDGE_RE` matches the link forms Mermaid accepts: plain, thick and dotted (`-.->`, `-..-`), the piped label, and the labelled forms `-- text -->`, `== text ==>` and `-. text .->` as one token. Dots mean a link only in the dotted syntax. The three diagrams from the report now come out unchanged, dotted links still work, and the nodes around a labelled link are still aliased. Three unit tests cover that; the first and third fail on the previous sanitizer. The six existing tests pass unchanged. * fix(wiki): keep shaped nodes and chained links intact in the Mermaid sanitizer Two regressions from the first version of this branch, both found in review: - Rounded and circular nodes became invalid: with the label masked, the node id regex still swallowed the opening parenthesis of `A(Start)` and the restored label landed outside the generated shape. The id now stops where a shape opens (`[`, `(`, `{`). A call-shaped id such as `doc()` — a masked, empty pair of parentheses right after the id — is still one id and is repaired to `doc["doc()"]` as before. - A chained link split one node in two: the labelled-link alternative of EDGE_RE took `--- file.ts ---` for one link with text, so the middle node skipped aliasing while the next line aliased the same id. A labelled link now opens only with a bare `--`, `==` or `-.` followed by a space; a complete link never does. Thick links too. Parser-backed tests in test/unit/wiki-mermaid-sanitizer-parse.test.ts run the output through mermaid 11.17.2, the release the viewer loads, under jsdom, and compare the parsed graph: shaped nodes at both ends yield the same vertices and edges as the input; the plain and thick chains yield exactly A, file_ts, B, C with the three links; the report diagrams, the labelled link and the call-shaped id are checked the same way. mermaid and jsdom join the package's devDependencies for that file only. * fix(wiki): read compact head links as links and keep a call-shaped id's member chain Two cases from review: - `A --xfile.ts --> B`: Mermaid's lexer takes the longer match, so `--` followed by `-`, `>`, `x` or `o` is a link with a tail whatever comes next. The labelled-link opener excluded `x`/`o` only before a space, so `--xfile.ts -->` was taken for one labelled link and `file.ts` skipped aliasing on that line while the next line aliased it. The opener now excludes those four characters outright. - `foo().bar --> B`: the empty-parentheses rule absorbed `()` into the id but left `.bar` in the suffix, which was emitted after the generated label. The member chain that follows is part of the id, so `foo().bar` is one node again, `foo___bar["foo().bar"]`, as the previous sanitizer produced. Parser-backed tests for the compact cross and circle forms (three edges on one middle node) and for a qualified call-shaped id at both ends of a link. Both fail on the previous commit. * fix(wiki): preserve Mermaid graph identity with tiny parser coverage --------- Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> |
||
|
|
68c0cdf198
|
fix(wiki): pass real page slugs into parent and overview prompts (#3710)
* fix(wiki): pass real page slugs into parent and overview prompts Signed-off-by: rome-xi <rome-xi@users.noreply.github.com> * style(wiki): format missing-child prompt fallback Address the outstanding formatting review on PR #3710. --------- Signed-off-by: rome-xi <rome-xi@users.noreply.github.com> Co-authored-by: rome-xi <rome-xi@users.noreply.github.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> |
||
|
|
5c651091dc
|
fix(cli): write the AGENTS.md/CLAUDE.md block in the file's own line ending (#3708)
Some checks failed
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Gitleaks / gitleaks (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
Devcontainer Smoke / Config-transform unit tests (push) Has been cancelled
Devcontainer Smoke / Build devcontainer image (default) (push) Has been cancelled
Devcontainer Smoke / Build devcontainer image (ui) (push) Has been cancelled
Publish Development Images / publish (core) (push) Has been cancelled
Publish Development Images / publish (ui) (push) Has been cancelled
* fix(cli): write the AGENTS.md/CLAUDE.md block in the file's own line ending The GitNexus block is generated with LF. In a checkout where text files are CRLF (Windows, `core.autocrlf=true`, `* text=auto`), upserting it into an existing CLAUDE.md wrote the block in LF inside CRLF prose (#3688). `git ls-files --eol` then reported `w/mixed`, and `git status` listed the file as modified after every reindex although `git diff` was empty once normalised. The count-only guard from #2907 never engaged there either: the existing section still had its CRLF, the fresh block had not, so the two never compared equal and the file was rewritten on every run. `upsertGitNexusSection` now reads the file, notes the ending of its first line break, normalises the content to LF for the marker search and the comparisons, and writes the result back in the file's ending, on the replace and append paths alike. A new file is still written in LF. `refreshBaseRefLine` edits in place and already kept the ending. Four tests: a section replaced in a CRLF file comes back all-CRLF with the hand-written prose untouched; a block appended to a CRLF file is CRLF; a count-only reindex leaves a CRLF file byte-identical; an LF file stays LF. The first three fail on the previous writer. * fix(cli): repair a context file the previous writer left with mixed endings A CLAUDE.md with CRLF prose and an LF GitNexus block, as the previous writer left it, compared equal to the generated content once normalised and took the preserved path, so its mixed bytes stayed and git kept reporting it modified. Before any path that leaves the file as it is, the original bytes are compared with the content in the file's own ending; when they differ, the file is written back once in that ending with its text, counts and custom sections untouched. Test: a file built that way is uniform after one run, with the hand-written prose and the block intact, and byte-identical after the next. * test(cli): cover CRLF context preservation paths --------- Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> |
||
|
|
146f549886
|
fix(eval): keep candidate code out of agent comparisons (#3715)
* fix(eval): isolate MCP runtime from coding agents Expose only a per-cell stdio socket to the release comparison agent while keeping candidate code in a separately supervised read-only sandbox. Require host-owned runtime exposure receipts for release and PR evidence. Preserve the 48 historical measurements with their source-exposure caveat and document the corrected post-merge comparison. Add transport, sandbox, cleanup, and report rejection coverage. Related: #3500 Related: #3609 * fix(eval): address CodeQL transport review findings Remove the redundant endpoint initializer, explain expected process-scan races, and limit worker error capture to ordinary exceptions while keeping teardown failure reporting intact. * fix(eval): synchronize socket transport tests --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> |
||
|
|
c7d0607d90
|
chore(deps): consolidate body-parser and fast-uri updates (#3716)
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> |
||
|
|
36227d93d1
|
perf: benchmark scope resolution and remove redundant allocation (#3711)
* fix(java): bound imported return-type propagation * fix(java): preserve inherited return types * perf: benchmark scope-resolution allocation in CI Measure all supported languages with real parsing and resolution, and require stable graphs and measured binding budgets in CI. Keep transitive Java factory calls without re-copying dependency tables, and build shared re-export closures only when a lookup needs them. * test: cover scope benchmark CI dependency --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> |
||
|
|
d8a256a03a
|
fix(taint): avoid false positives for guarded Git arguments (#3703)
* fix(taint): refine values after accepted guards Preserve bounded value restrictions through local flows and function summaries. Check the complete command against the modeled sink use before suppressing a finding. Unknown paths and exhausted budgets retain conservative findings. Refs #3490 * fix(taint): preserve guard proof identities Reject destructured/rest formals before they inherit a whole-argument proof, and include transitive alias uses in bounded guard certificates. Add worker regressions, argument-heavy scaling coverage and global refinement fallback diagnostics. Refs #3703 * perf(cfg): reuse scope and argument facts Reduce duplicate native AST reads and allocation in TypeScript CFG collection while preserving argument forwarding and malformed-input recovery. Add regression cases and a portable paired benchmark with recorded results. * test(cfg): distinguish validator cases --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> |
||
|
|
5dd58a5eec
|
chore(deps): consolidate updates and vendor compatible VECTOR (#3709)
* chore(deps): consolidate open Dependabot updates Combine the lockfile updates from #3698, #3701, and #3702: ip-address 10.7.4, zod 4.6.7, and js-yaml 5.5.0 with argparse 3.0.3. Preserve each upstream package record and all unrelated dependency pins. Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(lbug): vendor matching VECTOR extensions --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
11a49a15c6
|
fix(cli): reap embedding sidecar after query (#3693)
* fix(cli): reap embedding sidecar after query * fix(cli): drain hybrid search before cleanup * fix(cli): reap sidecar after group query --------- Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> |
||
|
|
0f3bad23b9
|
feat(web): resolve a root-relative GITNEXUS_BACKEND_URL against the page origin (#3640)
* feat(web): resolve a root-relative GITNEXUS_BACKEND_URL against the page origin A GITNEXUS_BACKEND_URL of `/` (or `/prefix`) is injected as-is, and the web client resolves it against the origin that served the page, so one gitnexus-web deployment reaches its API on every hostname a reverse proxy gives it. Protocol-relative values are left to the URL check and rejected. Documents the two ways to route /api/* behind a proxy. Closes #3593 * fix(web): reject a backslash-prefixed same-origin path The URL parser reads a backslash as a slash, so `/\host` resolved against the page origin became `http://host/`, an origin change. Both the server check and the client resolution now treat it like `//host`. * fix(web): keep a configured base ending in /api, and drop the unsupported direct route normalizeServerUrl stripped a trailing /api from every URL, so a deployment configured with GITNEXUS_BACKEND_URL=/proxy/api passed health and discovery and then connected to /proxy/repo. The deployment's own base is now kept as configured; the /api cleanup stays for hand-entered URLs. A regression covers health, discovery and repository selection under /proxy/api. The README no longer offers routing /api/* straight to `gitnexus serve` on a public hostname: serve refuses to start with GITNEXUS_PUBLIC_ORIGIN set until it has authentication of its own. * fix(web): keep only a same-origin base as configured The bypass in normalizeServerUrl matched any injected default, so an absolute GITNEXUS_BACKEND_URL ending in /api would have produced a doubled /api segment, and one carrying credentials would have kept them. Only a root-relative deployment base is kept now; it is resolved from the page origin, so it has neither. Absolute injected URLs are normalized as before. |
||
|
|
69a3ab1d58
|
chore(deps)(deps): bump dompurify (#3699)
Some checks are pending
Gitleaks / gitleaks (push) Waiting to run
Devcontainer Smoke / Config-transform unit tests (push) Waiting to run
Devcontainer Smoke / Build devcontainer image (default) (push) Waiting to run
Devcontainer Smoke / Build devcontainer image (ui) (push) Waiting to run
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Publish Development Images / publish (core) (push) Waiting to run
Publish Development Images / publish (ui) (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
Bumps the npm_and_yarn group with 1 update in the /gitnexus-web directory: [dompurify](https://github.com/cure53/DOMPurify). Updates `dompurify` from 3.4.16 to 3.4.17 - [Release notes](https://github.com/cure53/DOMPurify/releases) - [Commits](https://github.com/cure53/DOMPurify/compare/3.4.16...3.4.17) --- updated-dependencies: - dependency-name: dompurify dependency-version: 3.4.17 dependency-type: direct:production dependency-group: npm_and_yarn ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
466d56280a
|
fix(eval): retain pending completion notifications (#3700)
Claude Code can announce a background completion before the foreground result. Carry the notification across that boundary and consume it once when validating the continuation, preserving complete session accounting. Cover both event orderings with the pinned CLI and reject notification reuse. Reproduces the failure from Actions run 39948470804. Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> |
||
|
|
f3fa67b144
|
fix(routes): link consumers to wrapper-composed URLs (#3697)
* fix(routes): resolve wrapper URL prefixes Carry proven URL composition through lexical bindings and wrapper calls so consumers match the runtime mount path. Refs #2858 * fix(review): apply review findings * fix(routes): address PR review feedback (#3697) * fix(routes): avoid native parent walks in URL facts --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> |
||
|
|
62a6f67083
|
chore(deps)(deps): bump ignore from 7.0.12 to 7.0.13 in /gitnexus (#3690)
Bumps [ignore](https://github.com/kaelzhang/node-ignore) from 7.0.12 to 7.0.13. - [Release notes](https://github.com/kaelzhang/node-ignore/releases) - [Commits](https://github.com/kaelzhang/node-ignore/compare/7.0.12...7.0.13) --- updated-dependencies: - dependency-name: ignore dependency-version: 7.0.13 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> |
||
|
|
5726ad42f0
|
chore(deps): consolidate all open Dependabot updates (#3682)
* chore(deps): consolidate Dependabot updates Keep all twelve dependency PRs in one verified update. Refresh the workflow contract test for the new exact Claude action pin. * test(eval): align setup-uv action pin --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> |
||
|
|
48f108a58a
|
docs: simplify guides and verify receiver gaps (#3694)
* docs: align resolution architecture and roadmap * docs: add language ingestion gap inventories * test: audit receiver gap targets and coverage Check exact declaration IDs, expose the Dart await wrong-owner edge, and execute previously excluded C++, Rust, PHP, Dart and Vue shapes. Refresh the baseline and all language inventories from fresh measurements; call-drop counts remain unchanged. * docs: focus roadmap and shorten project README Keep the resolution roadmap limited to future work and acceptance criteria. Move detailed editor, CLI, MCP, deployment and wiki instructions into linked guides, preserve their command examples, and correct stale onboarding claims. * docs: correct setup and audit descriptions --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> |
||
|
|
bbbc1b49e3
|
test(bench): type the JS, Ruby and PHP receiver shapes; mark C dispatch N/A (#3498) (#3663)
Some checks are pending
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Gitleaks / gitleaks (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Classify release event (push) Waiting to run
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
* test(bench): type the JS, Ruby and PHP receiver shapes; mark C dispatch N/A
Several shape-arm rows were measuring their fixtures instead of the receiver
spelling.
The JavaScript and Ruby rows had no types anywhere, so each chain cell said
"this code is untyped" and could only flip by guessing from a method name.
They now carry JSDoc and YARD types, the way plain JS and Ruby carry types.
Each row keeps one untyped control as a new extra id, `untypedChain`, so
`plainChain` and the other canonical ids mean the same thing in every row.
Ruby names each function's parameter after its cell, because YARD `@param`
bindings land on the Module scope. A probe showed an untyped `svc` resolving
through a sibling's tag.
PHP `plainDeepChain` and `optionalChain` went through the unannotated
`getUser()`, so they measured the missing return type rather than their own
spelling. They now use `getUserTyped()`, and `arrowCallChain` stays the
unannotated control. `indexElement` gains PHPDoc `@param User[] $repos`.
The C structs lived in `models.h`, which the C++ provider owns, so C never saw
them. They now live in `main.c`. All three measured C cells called through a
function-pointer field. That field has no graph node and no static callee, so
the cells could never resolve and are now N/A.
Shape-arm movement, every target checked:
- javascript: plainChain, plainDeepChain, optionalChain, awaitParen
VISIBLE-GAP -> RESOLVES; untypedChain new, VISIBLE-GAP; indexElement
stays VISIBLE-GAP (no JS elementTypeOf)
- ruby: plainChain, optionalChain VISIBLE-GAP -> RESOLVES; plainDeepChain
VISIBLE-GAP -> INVISIBLE-GAP (base types, untyped attr_reader step);
untypedChain new, VISIBLE-GAP; indexElement stays INVISIBLE-GAP
- php: plainDeepChain, optionalChain INVISIBLE-GAP -> RESOLVES
- c: plainDeepChain, indexElement VISIBLE-GAP -> N/A; fieldReceiverCall
INVISIBLE-GAP -> N/A
The count arm and every other language's cells are unchanged. No resolver
code changed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: correct typed shape benchmark states
* docs: qualify Ruby typed shape owner claims (#3663)
The receiver shape gate matches member names only, so the Ruby
plainChain/optionalChain owner (User.save) is a probe observation, as
already stated for the JavaScript and PHP rows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: drop unverified #3609 tracking claims (#3663)
#3609 does not document the Ruby YARD @param scope leak or C header
struct visibility, so say both are not yet filed separately.
Also carry the BASELINE.md notes that came from the pre-rebase merge commits
(main
|
||
|
|
09b7a5b174
|
fix(ingestion): read more receiver-chain spellings (#3498) (#3667)
* fix(ingestion): name chain steps without their explicit type arguments C#, C++ and Rust put explicit type arguments inside the callee node, not beside it as TypeScript, Java and Kotlin do. The receiver-chain walk read the callee's text, so `svc.GetTyped<User>().Save()` built the step `GetTyped<User>`, which matches no member, and C++ built `getTyped<User>`. Rust's `svc.get_typed::<User>()` wraps the whole field access in a `generic_function`, so the walk found neither a name nor the inner receiver and built no chain at all. The walk now peels these callee nodes (C# `generic_name`, C++ `template_method`, `template_function` and `dependent_name`, Rust `generic_function`) before it reads the step name and the receiver. It is a node-type table next to the existing ones; nothing branches on a language. Type arguments choose an instantiation, never a member, so the name loses nothing. Shape arm: csharp and rust `explicitTypeArgs` go from VISIBLE-GAP to RESOLVES, to `User.Save`/`User.save`. cpp `explicitTypeArgs` keeps its drop: the step is now `getTyped`, but `template <typename T> T* getTyped()` has no return type until `T` is substituted, which is separate work. baseline.json and BASELINE.md record the move; the count arm and a corpus A/B against main are unchanged. The chain wire format is unchanged, so the receiver-chain codec version stays at 2. Parse caches need no manual bump: the parse-cache identity hashes every file under `core/ingestion`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ingestion): read receiver chains through C# `!` and Swift `(x)` The chain walk peels wrappers that denote exactly their operand, so the chain's base is `svc` rather than `svc!` or `(svc)`. Two spellings of that were missing. C#'s null-forgiving `svc!` is a `postfix_unary_expression`, so `svc!.GetUser().Save()` built `2|svc!|cGetUser`, whose base binds nothing. It now joins the `!`-gated entry Swift and Kotlin already use. The same node type carries `x++` and `x--`, and the `!` gate leaves those alone. Swift has no parenthesized-expression node: `(await svc.getUserAsync())` and `(holder)` parse as a one-element `tuple_expression`, and the walk stopped there. `(await svc.getUserAsync()).save()` built no chain, and `(holder).repo.save()` built one whose base `(holder)` binds nothing. A new gate peels a tuple only when it is exactly `(`, one element, `)`. That excludes Rust's real one-element tuple `(svc,)`, Swift's labelled `(x: svc)`, and every tuple of two or more. Shape arm: csharp `nonNullAssert` goes from VISIBLE-GAP to RESOLVES (`User.Save`). Swift `awaitParen` now builds `2|svc|cgetUserAsync|a`, but it was already a VISIBLE-GAP and stays one: Swift return types live on the Module scope, and the fold looks there only after the Swift/Dart hoist change lands. Swift `(holder).repo.save()` now resolves to `User.save`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ingestion): walk C# null-conditional access in receiver chains C# parses `svc?.GetUser()` as a `conditional_access_expression` whose receiver sits in a `condition` field and whose member sits in a `member_binding_expression` child. The chain walk read it like any other callee: it named the step `.GetUser` and found no inner receiver, so `svc?.GetUser().Save()` built no chain and could never resolve. As a receiver (`user?.Address.Save()`, `repos?[0].Save()`) the node stopped the walk, and its whole text became the base. A node-type table now says where a conditional access keeps its receiver. As a callee, the walk names the step from the member binding; an element binding (`handlers?[0]()`) names no member. As a receiver, a member binding becomes a field step and an element binding becomes an index step. Shape arm: csharp `optionalChain` goes from VISIBLE-GAP to RESOLVES (`User.Save`). `svc?.GetTyped<User>().Save()` also resolves, through the type-argument peel from the earlier commit. `repos?[0].Save()` now records its miss, which was silent before: `User[] repos` has no type binding yet. `user?.Address.Save()` already resolved through the receiver-text cascade; a resolver row pins it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ingestion): read Ruby and Swift subscripts as index steps Two grammars spell a subscript as something the chain walk did not recognise. Ruby's `repos[0]` is an `element_reference`, which was missing from the subscript table, so the walk stopped there. The receiver has no `.` or `(` either, so the call vanished: no edge and no drop. Swift has no subscript node at all. `repos[0]` parses as a `call_expression` whose `call_suffix` holds `value_arguments`, the same as a call, except that the list is delimited by `[`/`]` instead of `(`/`)`. Walked as a call, it named no member, and `holder.users[0]` named `users` as if it were a method. A call node whose suffix argument list opens with `[` is now read as an index step. The opening token is the only thing that tells the two apart, and `svc.getUser(0)` stays a call. Shape arm: ruby `indexElement` goes from INVISIBLE-GAP to VISIBLE-GAP. `repos` is untyped, so there is nothing to fold, but the miss is now counted. swift `indexElement` builds `2|repos|i` and records its miss too, going from INVISIBLE-GAP to VISIBLE-GAP; resolving it needs a Swift `elementTypeOf` hook from a separate change. So does `holder.users[0].save()`, which now builds `2|holder|fusers|i` instead of a call step named `users`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: clarify receiver walker review limits * test: pin receiver-chain walker control spellings (#3667) - Kotlin calls share Swift's `call_suffix → value_arguments`; pin that a parenthesized and a generic Kotlin call stay call steps, never index steps. - Pin that a Swift trailing-closure call stays a call step. - Pin that C# `GetTyped<T>()` and C++ `ns::getTyped<T>()` without a member receiver are named for the member and mint no chain. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: skip Swift chain rows when its grammar is unavailable (#3667) The walker rows loaded the optional vendored Swift grammar at module scope, so a host without its prebuild failed to collect every other language's test in the file. Gate Swift on isLanguageAvailable and report its rows as skipped; every other row still runs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: expect Swift awaitParen through hoisted returns (#3667) Rebased onto #3666, whose hoisted Swift return types let the walker's `(x)` chain resolve awaitParen to User.save. This carries the content the replaced merge commits held: the walker row now expects that edge and no drop, the receiver baseline marks swift awaitParen RESOLVES, and BASELINE.md keeps the measured main and stack integration notes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(bench): label parent-only awaitParen note and drop issue claim (#3667) The #3666 section said Swift awaitParen still needed a fix. That holds for #3666 alone; combined with this walker it resolves to User.save with no drop, while dart nonNullAssert stays unsupported. Also stop calling the generic-arity limitation tracked by #3609, a general triage tracker. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
cc0b5d54f7
|
fix(swift,dart): type chained receivers through hoisted return types (#3498) (#3666)
* fix(swift,dart): type chained receivers through hoisted return types (#3498) Swift and Dart hoist every method's return-type binding out of the class body onto the Module scope, as C# does, but neither set hoistTypeBindingsToModule. The receiver walk therefore never looked there, and svc.getUser().save() had no receiver type. Both now set it. The flag alone was not safe. The hoisted bindings are keyed by bare method name, so with class A { getUser() -> User } and class B { getUser() -> Admin } in one file the Module scope keeps only B's entry, and a.getUser().save() resolved to Admin.save. C# and TypeScript already do this on main. So there is a new ScopeResolver toggle, hoistedTypeBindingsRequireOwner, which Swift and Dart also set. With it, the walk accepts a hoisted binding only when its declaration anchor encloses the declaration of a member of that name owned by the receiver's class. Otherwise it keeps climbing, and declines if nothing passes. The four walk-ups in compound-receiver.ts now share one helper. The toggle is opt-in. With the guard on everywhere, TypeScript loses correct edges where two same-named methods happen to return the same type (11 rows in inferred-field-receiver-matrix), because the Module map cannot hold both. Swift and Dart never had those edges. Bench: swift plainChain, plainDeepChain, optionalChain and nonNullAssert (INVISIBLE-GAP) and dart plainChain, plainDeepChain and optionalChain (VISIBLE-GAP) now RESOLVE with correct targets. No other cell and no count-arm figure moves. The inferred-field matrix gains correct Inner.compute edges for Swift (3 rows) and Dart (7 Alien rows). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(scope): guard hoisted return ownership * fix(scope): gate hoisted return replay checks * fix(scope): preserve parameterized return replay Keep exact callee return facts when ancestor candidates have matching known parameter types, while refusing same-owner overload ties. Refs #3666 * fix(scope): preserve known variadic return replay Compare complete captured parameter vectors across distinct ancestor owners, including variadic methods with unknown numeric arity. Keep unknown Swift parameter slots instead of compressing them away. Refs #3666 * test(swift): retain reviewed receiver fixtures * fix(scope): retain unknown overload candidates Keep unknown parameter slots possible without hiding conversion overloads. Check return-owner precision without requiring valid calls to stay unresolved. Refs #3498 * fix(scope): preserve conversion suppression Keep explicit empty conversion results final when parameter metadata is unknown. Add exact positive controls for receiver-fixture liveness. Refs #3498 --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
673dc6968e
|
fix(rust): see through Box, Rc and Arc on struct field types (#3498) (#3665)
* fix(rust): see through Box, Rc and Arc on struct field types (#3498) The struct-field normalizer in rust/range-binding.ts cut a type at its first `<`. A `repo: Box<User>` field was typed `Box`, so `self.repo.save()` bound nothing. It also stripped a leading `&` but not a lifetime, so a borrowed field, which always carries one (`&'a User`), came out as `'a User`. Box, Rc and Arc auto-deref on a method call, so the field now takes their first type argument: bare or path-qualified (`std::rc::Rc<User>`), and nested (`Rc<Box<User>>`). Borrows with a lifetime or `mut` are stripped, and `Box<dyn Trait>` reduces to the trait through the stripDynBound helper that parameters already use (#2604). RefCell, Cell, Mutex, RwLock, Option and Vec are deliberately left alone. A method on the inner type needs `.borrow()`, `.get()`, `.lock()`, `.read()`, a match or an index first, so the receiver really is the wrapper, and a direct call through one must not bind. Shape arm: rust `decoratedFieldType` moves INVISIBLE-GAP -> RESOLVES, with the edge on `User::save`. The corpus has no smart-pointer or borrowed struct field, so the count arm and every other cell are unchanged, and the corpus edge (2838) and drop (192) sets match main exactly. Scope-capture fingerprints are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(rust): keep opaque pointer fields wrapped * fix(rust): refuse opaque macro expansions * test(rust): pin pointer wrapper targets and opacity limits (#3665) - Assert a same-file Box keeps its own save target while User.save stays unbound (review thread on custom same-named wrappers). - Pin the documented workspace-wide recall limit: an unrelated unresolved import or macro_rules! keeps an otherwise valid pointer field wrapped. - Drop per-case logging and group the opaque/expansion cases in describe blocks. - Collapse the stacked pointer-field receipts in BASELINE.md into one current section listing every workspace-wide refusal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: preserve Rust integration notes * test(rust): assert complete pointer targets --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
af5fd9665c
|
fix(csharp): bind nullable field types for receiver resolution (#3498) (#3664)
* fix(csharp): bind nullable field types for receiver resolution (#3498) A C# field written `User? repo;` got no type binding. Its type is a `nullable_type` node, and the field_declaration rules only matched identifier, generic_name and qualified_name types. Parameters, properties, method returns and foreach variables already have a nullable_type rule, so fields were the odd one out: the class scope had no binding for `repo`, and `this.repo.Save()` was dropped as receiver-unresolved. Add the missing field rule. interpretCsharpTypeBinding already strips the `?`, so the field binds `User`, as the property spelling does. Shape arm: csharp `decoratedFieldType` moves VISIBLE-GAP -> RESOLVES, with the edge on `User.Save`. No corpus fixture declares a nullable field, so the count arm and every other cell are unchanged, and the corpus edge (2838) and drop (192) sets match main exactly. Scope-capture fingerprints are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(csharp): preserve nullable field ownership * fix(csharp): refuse unowned nullable types * test(csharp): guard nullable-member fixture cleanup (#3664) Address nexus-check review on resolvers/csharp.test.ts:1471: skip the temp-root removal when mkdtempSync never ran, so a setup failure is not masked by a secondary rmSync(undefined) TypeError. Matches the guard in csharp-nullable-field-review.test.ts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(bench): scope nullable wrapper claim to what the code does (#3664) The ownership-controls note said the nullable-field marker preserves custom generic wrappers. Its only control, Custom.List<User>?, passes through the qualified-type refusal instead. State that unqualified wrappers keep the wrapper type (no native control yet) and that qualified spellings, custom wrappers included, stay unbound. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: preserve C# integration notes * fix(csharp): preserve generic argument qualifiers Ignore comments when checking outer nullable-field qualification. Refs #3664 --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
36e16434ea
|
fix(php): type locals from namespaced-function factory calls (#3498) (#3662)
* fix(php): type locals from namespaced-function factory calls
`namespace App; use App\Mod as rp; $x = rp\make(); $x->m()` left `$x`
untyped although `make` is declared `: C` in `namespace App\Mod`, and the
inline `rp\make()->m()` recorded a drop. The query matched only an
unqualified callee (`function: (name)`), so a `qualified_name` callee minted
neither a call site nor a result binding; `\App\Mod\make()`, `Mod\make()`
and `namespace\make()` failed the same way, and nothing could have expanded
the path anyway: functions were never indexed by fully qualified name.
- A `qualified_name` / `relative_name` callee is captured as a free call
(tail name, written path as `@reference.qualified-name`) and as a
call-result binding that keeps the whole path, leading `\` included.
- New `php/qualified-function.ts` expands that path by PHP's name rules:
`\` is absolute, `namespace\` is the file's namespace, and a qualified
head goes through the file's class/namespace `use` table, else the file's
namespace. It indexes every `Function` def by lower-cased FQN per pass and
answers only when exactly one def declares the name.
- PHP implements `resolveQualifiedCallee` and `resolveQualifiedFreeCall`
with that one resolution, so a call's type and its CALLS edge agree.
`Qff\Decoy\make` in the fixture is never picked.
- A receiver bound to an in-program namespaced function with no return type
(`$x = rp\untyped()`) now records a `receiver-unresolved` drop.
A full lang-resolution A/B against the parent removes no CALLS/ACCESSES edge
and no drop. It adds 20 edges, all inside the new
php-qualified-function-factory fixture, and 2 drops (its two untyped sites).
The PHP capture golden gains the new fixture only. Existing fixtures' digests
are byte-identical.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(php): bind use-function imports outside PSR-4 by fully qualified name
`use function App\Mod\make; $x = make(); $x->m()` typed nothing, and the
call itself had no CALLS edge, when `make` is declared outside any PSR-4
path: loaded through composer `autoload.files`, or in a project with no
composer.json. Import resolution finds a function only through a PSR-4
directory or a file named after it, so the import bound nothing (or, with a
PSR-4 directory that holds other files, resolved to one that declares no
`make`). In the new fixture, `namedCall` even picked up another `make`'s
return type, mirrored by name from a sibling file, and emitted a wrong
`QffGadget.ping` edge.
After namespaces are known, a `use function A\f` whose module scope binds
nothing for its local name is bound to the one workspace function named
`A\f`, through the same per-pass FQN index as qualified callees. The fully
qualified name is the function's identity, so this guesses nothing; a
resolved import or a local declaration is left alone, and two declarations
of one name bind nothing. The IMPORTS edge is unchanged: finalize has no
namespaces yet.
Lang-resolution A/B against the parent: one wrong edge removed
(`namedCall -> QffGadget.ping`), two correct fixture edges added
(`namedCall -> make`, `namedCall -> QffWidget.ping`), and four correct
edges the combined corpus lacked because its merged composer configs map
`App\` to many fixtures: php-calls `create_user -> write_audit`,
php-overload-dispatch `run -> format_text` and `run -> format_text_padded`,
php-use-function-const `Calculator.process -> formatName`. Each FQN is
declared once in the corpus. Drops are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(bench): rebaseline php captures and receiver counts
- receiver-resolution: the new php-qualified-function-factory fixture's two
untyped sites (`$x = rp\untyped(); $x->ping()` and inline
`rp\untyped()->ping()`) are each one call drop: call drops 154 -> 156,
all kinds 224 -> 226. No other drop moves in a same-corpus A/B, and
shape-arm cells are unchanged.
- scope-capture (php): corpus growth only. The query now captures
qualified function callees, but no existing fixture has one: parking the
new fixture reproduces the prior fingerprint byte-for-byte. It adds 96
capture groups and 9 fixture entries.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(php): fail closed on qualified calls the path cannot resolve
A qualified call site was named by its tail (`count` for `\count($x)`),
so when `resolveQualifiedFreeCall` refused the path, the free-call pass
fell through to its simple-name tiers and could bind a same-named
function in the caller's namespace: `\count($x)` -> a user `Qff\Ext\count`,
`\Vendor\Pkg\make()` -> `Qff\Ext\make`, and a twice-declared
`\Qff\Dup\make()` the same way. Before this branch such calls minted no
site at all, so these were new wrong edges.
The site is now named by its whole written path, the same text it carries
as `@reference.qualified-name`. The provider hook still resolves it by
PHP's name rules. Every simple-name tier (scope chain, implicit `this`,
global unique-name fallback) looks up a name with a `\` in it and finds
nothing, so a refused path (builtin, uninstalled package, ambiguous name,
multi-namespace file) leaves the call unresolved. This change is PHP-only.
Lang-resolution A/B against the parent: no edge or drop removed. Added
edges: the three new fixture functions' `new QffWidget()` calls. No edge
from builtinCall, vendorCall or ambiguousCall. Versus the pre-branch base:
29 edges added (25 in the fixture, plus the 4 `use function` edges from
|
||
|
|
e7ef82dfcb
|
fix(kotlin): type locals from package-qualified factory calls (#3660)
* fix(scope): follow namespace factory return types and record untyped drops `import mod as rp; x = rp.make(); x.m()` lost the receiver type even when `make` is declared `-> C`: `x` was bound to the raw dotted name `rp.make`, and nothing followed a dotted name to a function's return type. The inline `rp.make().m()` failed the same way in the compound resolver, which read `ns.f()` only as a construction. Resolve `ns.f` to `f`'s return annotation when `ns` is a namespace import, the fact `propagateImportedReturnTypes` already mirrors for a named import. The post-finalize chain follow uses it for bindings and the compound resolver for inline calls. Only sites that emitted nothing change: the full lang-resolution corpus emits the identical 2,838 CALLS/ACCESSES edges. Record the drops that left `impact` at "exact": - a plain receiver bound to a namespace function with no usable return type (`x = ns.untyped()`); - an outer call whose `file:line:col` key a resolved inner site already claimed (`ns.untyped().m()`, `s.repo.Delete(id)`). Only a duplicate capture of the same member hides a drop now. Receiver baseline: call drops 128 -> 151, reads 44 -> 48, ten shape-arm cells INVISIBLE-GAP -> VISIBLE-GAP; every new drop has no CALLS edge to its member (BASELINE.md lists them). The python.factory-qualified accuracy gap is removed; the python-scope fingerprint and capture golden gain the new fixture only. Fixes #3498 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(dart): type locals from prefixed-import factory calls `import 'mod.dart' as rp; final x = rp.make(); x.m()` gave `x` no type: local call inference accepted only a bare callee (`Callee()`), and the prefix-stripping type normalizer would have reduced `rp.make` to `make`. Bind a local initialized by exactly `prefix.member(...)` (direct or awaited) to the dotted callee and keep its prefix, so the shared chain follow types it from the namespace function's return annotation. Only call-derived bindings keep the prefix; annotated `prefix.Type` spellings are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(zig): type locals from namespace-module factory calls `const rp = @import("mod.zig"); const x = rp.make(); x.m()` bound `x` to the namespace handle `rp` itself: the call-return rule reads a non-value head as the type being constructed (`Counter.init()`), and import bindings are not values. The member name was gone before the shared chain follow could use the new namespace return-type lookup. For a module-level handle on a lowercase file (a namespace module, by Zig convention), bind the dotted callee `rp.make` so the shared chain follow types it from `make`'s return annotation. File-struct handles (`@import("Page.zig")`, any spelling of the handle) keep naming the struct, so `Page.init()` is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(bench): rebaseline captures and receiver counts for Dart and Zig - receiver-resolution: the Dart fixture's untyped factory adds one call drop (151 -> 152); every typed form in the Dart and Zig fixtures resolves. - scope-capture (dart): two existing dart-method-enrichment locals initialized by `obj.method()` / `Type.method()` gain a call-derived binding, plus the new fixture's captures. A full lang-resolution A/B emits the identical edges outside the new fixtures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(scope): bind same-namespace inheritance bases before sibling visibility exists Cause: same-package / same-namespace visibility (`populateNamespaceSiblings`) is populated after the inheritance pre-pass and `buildMro`. A base written without an import (`class C extends B`, B in another file of the package) could bind only through the workspace-unique simple-name fallback, which any other package declaring a `B` defeats: no EXTENDS edge, an empty MRO, and lost calls through inherited members. In the combined lang-resolution corpus `java-grandparent-resolution` `c.greet().save()` dropped because `java-unrelated-method-collision` declares default-package A and B. Moving the sibling hook ahead of the pre-pass was measured and rejected. It retargeted correct edges and minted wrong ones, because the sibling channels carry candidates the pre-pass must not see: C# global-namespace types are consulted at every scope (same-file `Cat : Animal` bound another fixture's global Animal), namespace buckets merge across source roots with no proximity order (PHP, C#), and a qualified `extends app.base.Box` bound through a same-package `app.Box`. Fix: an optional `ScopeResolver.resolveImplicitInheritanceBase`, consulted by the pre-pass only when the shared chain bound nothing, the base was written bare (no qualifier, name capture at the start of the base expression), and no import claims the name. It adds edges and never retargets one. `resolveSameNamespaceInheritanceBase` picks the candidate in the deriving class's namespace, nearest by shared path prefix, refusing a tie. Java and Kotlin answer from their package facts, C# from the exact `namespacePrefix`. Corpus A/B (main vs this, same corpus): EXTENDS/IMPLEMENTS +42, all within their own fixture, 0 removed. CALLS: six `super.save()` edges keep their target but are now MRO-dispatched (`global`, as in isolation), one moves off another fixture's BaseModel.Save onto its own, and 7 are new (both grandparent `greet` calls, a default-method `validate`, the new fixtures). Receiver bench: call drops 128 -> 127, all kinds 192 -> 191, shape arm unchanged. Scope-capture fingerprints re-baselined for the added fixtures only (parked, --check passes on the old ones). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(java): resolve a duplicated package+type import to the importer's own root Cause: `buildJavaPackageIndex` kept the first file declaring each package+type. One repository can hold several build modules or sample projects that each declare `models.User`, so every `import models.User;` bound whichever copy was parsed first, a wrong IMPORTS edge at full confidence that carries the receiver types behind it. In the combined lang-resolution corpus 24 fixtures declare `models.User`, and e.g. `java-heritage` UserService.processUser called `java-assignment-chain/models/User.save`. Fix: the index keeps every declaring file in a sparse `duplicateTypes` map (only colliding pairs, so a normal workspace pays nothing), and a type import resolves to the declaring file sharing the longest leading path with the importer (its own module or source root). A tie, or an unknown importer, refuses rather than guessing. Wildcard imports are unchanged. Corpus A/B (previous commit vs this, same corpus): 40 CALLS edges that crossed into another fixture's User/Repo/Address/UserService are gone, 55 CALLS/ACCESSES edges now land in their own fixture (0 cross-fixture), and `java-parent-resolution` User IMPLEMENTS Serializable moves off `java-heritage` onto its own interface. Receiver-unresolved drops unchanged; import-target and receiver benches pass. Scope-capture java fingerprint re-baselined for the added fixture only (parked, --check passes on the old one). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(scope): expose the position a receiver-chain fold reaches `foldReceiverChain` answers "which class", so a chain whose last step is declared as a type the program does not contain (`Dictionary<string, User>`, `String`) reads only as a miss. That miss is informative to the receiver-origin classifier: every hop was typed by a program declaration and the last one names a library type. Extract the fold loop into `foldReceiverChainState` and export `foldReceiverChainPosition`, which returns the final position including one whose declared type named no class, and reports nothing to `recordReceiverType`. `foldReceiverChain` keeps its behaviour: it now wraps the same loop and makes the receiver-type report from the final state, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(scope): classify library-rooted receiver drops from provider evidence `classifyReceiverOrigin` had one source of positive external evidence, `LanguageProvider.isBuiltInName`. That set also filters callee names out of the call graph, so it could not grow to cover host globals, stdlib imports or library-typed values without dropping real edges, and every such drop hedged `impact()` to `lower-bound`. It also read a base name as evidence even where the program bound that name itself: a parameter named `console` with an unresolved type, or an unresolved import of a built-in spelling from the program's own module, came back `external`. Add two provider data hooks read only by the classifier: `externalReceiverNames` (globals, platform roots, library types) and `isExternalModuleSpecifier` (import specifiers reserved for the runtime or stdlib). The classifier now answers `external` when: - the base is unbound at the site and is a built-in or listed name; - the base is bound only by imports that resolved to no program file and all name a library module; - the receiver chain folds, through declarations each owner makes, to a position declared as a library type (`this.data.Keys` with `Dictionary<string, User> data`), and the program declares nothing by the dropped member's name (an extension method or trait impl could be the real target). The fold skips ancestor-hoisted bindings, which can lend another class's member type to the step. A base the program binds (a parameter, a local, a resolved import, an import from a non-library module) never takes a name-based verdict. The in-program arms are unchanged and still win first. No drop is added or removed and no edge changes. Java supplies its reserved `java` package root for both hooks: the JVM refuses to define classes under `java.*`, so `java.util.Optional.of(...)` and `import java.util.Map` are rooted in the platform. Without the specifier hook, the stricter import rule would have demoted the existing `import java.util.List; List.of(...)` verdict to unknown. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(typescript): name JavaScript host globals as the program boundary `document.body.appendChild(...)`, `window.history.pushState(...)` and `Buffer.from(raw).toString()` were recorded `unknown`: none of `document`, `window`, `Buffer`, `process` or `globalThis` is in `BUILT_INS`, and they cannot be added there because that set also filters callee names, so `process` would drop every call to a program function named `process`. Each drop hedged `impact()` to `lower-bound` although no program node could have been the target. List the host objects in `EXTERNAL_RECEIVER_NAMES` and supply it to the TypeScript, JavaScript and Vue providers as `externalReceiverNames`. A name counts only where the program binds nothing by it and declares nothing by it, so a `document: Doc` parameter, an untyped `window` parameter, a local `navigator` and a program `class Buffer` keep their verdicts. `location`, `history`, `self` and `crypto` stay out: they are ordinary local names. Corpus: 5 call drops move from unknown to external. `process.env.*` (3 reads) stays in-program there because three fixtures declare a top-level `function process()`, one of them in a global script. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(python): name standard-library imports as the program boundary `os.environ.setdefault(...)` after `import os`, and `Path(__file__).resolve()` after `from pathlib import Path`, were recorded `unknown`. The imports resolve to no file in the program, but an unresolved import alone cannot be told apart from an in-program module the resolver missed, and nothing said that `os` and `pathlib` belong to the interpreter. Supply `isExternalModuleSpecifier` from a curated set of standard-library modules, matched on the specifier's first segment (`os.path` -> `os`). A relative specifier never matches. The classifier still requires the import to have resolved to no program file, so a local `os.py` that the resolver finds wins, and a parameter named `os` is the program's own. Generic nouns an application plausibly uses for its own modules (`types`, `queue`, `signal`, `secrets`, `string`, `email`, `http`, ...) are left out. Third-party packages are not listed: an unresolved `fastapi` import stays `unknown`. Corpus: 4 call drops move from unknown to external (`os.environ.setdefault` x3, `Path(__file__).resolve()`). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(zig): name @import("std") and @import("builtin") as the program boundary `std.sort.pdq(...)` and `const Allocator = std.mem.Allocator;` were recorded `unknown`: `std` is bound by `const std = @import("std")`, an import the resolver deliberately leaves external, and nothing told the classifier that the module is the standard library rather than an in-program file it failed to find. Supply `isExternalModuleSpecifier` for the two module names Zig reserves: `std` and the compiler-generated `builtin`. Exact names only, so `@import("std.zig")` (a file path) stays `unknown`, as does `root` (the program's own root module) and any module `build.zig` names. Corpus: 9 drops move from unknown to external: `std.sort.pdq` (2 calls, 2 reads), `std.mem.Allocator` (1 read), and the `zig-qualified-literal` literals `std.Thread.Mutex{}` and `std.mem.Allocator{...}` (2 calls, 2 reads). The last four are that fixture's edge negative control (`useExternal` must emit no edge, which still holds); their receiver is std, so `external` is the right origin. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(csharp): name the System namespace and Dictionary as the program boundary `System.Console.WriteLine(...)` was recorded `unknown`: `Console` is in `BUILT_INS` but the chain base is `System`, which is not, and `this.data.Keys` with `Dictionary<string, User> data` was recorded `in-program` because the base `this` resolves to a program class even though the member lives on a BCL type. Supply `externalReceiverNames` with `System` (the BCL root namespace), `Dictionary` (the one collection capture does not reduce to its element, because it takes two type arguments) and `string` (a keyword alias that no program can declare). The base check covers `System.Console`; the chain fold reaches `Dictionary<string, User>` through the program's own field declaration. A program `class System`, a field of a program type, and an extension method the program declares on `Dictionary` all keep their verdicts. Corpus: 3 call drops move from unknown to external (`System.Console.WriteLine`). The two `this.data.Keys/Values` reads in `csharp-nested-member-foreach` stay in-program there: the fixture's global-namespace `App` is one of several in the merged corpus, so `this` binds another fixture's `App` and the fold declines. Analysed alone, both read `external`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(kotlin): name java.lang.System and the platform roots as the program boundary `System.out.println(...)` in Kotlin was recorded `unknown`: Kotlin's `BUILT_INS` lists stdlib function names, not `System`, which Kotlin/JVM imports by default from `java.lang`. Supply `externalReceiverNames` with `System` and the package roots no program can declare into (`java`, refused by the JVM; `kotlin`, reserved by the compiler for the standard library), and `isExternalModuleSpecifier` for imports under those roots. A program `object System` keeps the receiver `in-program`. Corpus: 1 call drop moves from unknown to external (`kotlin-object-literal-scope` `System.out.println`, whose decoy `println` on the enclosing object still gets no edge). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(bench): rebaseline receiver-origin buckets for library receivers The preceding commits label library- and runtime-rooted drops `external` instead of `unknown`. Call drops by origin move from 55 in-program, 44 external and 29 unknown to 55, 62 and 11. Total drops (192), call drops (128), every per-extension and per-shape count, and every shape-arm state are unchanged; a same-corpus A/B against main keeps all 2,838 CALLS/ACCESSES edges byte-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(rust): type locals from module-qualified factory calls `mod modx; use crate::modx as rp; let x = rp::make(); x.m()` left `x` untyped although `make` is declared `-> C`. The call-return rule kept only the text before the first `::` (the `Foo::new()` idiom), so `x` was bound to the module `rp` and the function name was lost. `modx::make()` and `crate::modx::make()` failed the same way, and the inline `rp::make().m()` failed in the compound resolver, which cannot split a `::` path. With a `let modx = Other;` in scope, `modx::make()` even typed `x` as `Other` and emitted a wrong `Other.m` edge. - Rust keeps the whole callee path when every segment is lower-case: a module function, by rustc's lint-enforced casing. `Foo::new()` and `Self::new()` still read as the type. - New `ScopeResolver.resolveQualifiedCallee` hook. The shared chain follow, the compound call branch and the drop recorder hand a callee text no scope binds to the provider, which returns the callable it names; the shared side reads that callable's return annotation by definition identity and never splits the path. Rust answers with the module-tree walk behind `resolveQualifiedFreeCall`, so a call's type and its CALLS edge come from the same resolution. - A receiver bound to an in-program module function with no return annotation (`x = rp::untyped()`) records a `receiver-unresolved` drop, as #3498 does for `ns.untyped()`. - `rp::make()` did not resolve as a call either: the module-name filter rejected the alias `rp`, and under Cargo the import binds the module's `mod modx;` declaration rather than `modx.rs`. The `use` candidate now admits an alias that names a module and reads a module from its top-level declaration. A full lang-resolution A/B against the parent emits identical CALLS and ACCESSES edges outside the new fixture, except one new correct edge: `rust-ambiguous` `main -> models::Handler.handle`, typed from `let h = services::create_handler()`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(bench): rebaseline rust captures and receiver counts - receiver-resolution: the new rust-qualified-factory-return fixture's two untyped sites (`x = rp::untyped(); x.m()` and inline `modx::untyped().m()`) are each a call and a callee-position read drop: call drops 152 -> 154, reads 48 -> 50, all kinds 220 -> 224. No other drop moves in a same-corpus A/B, and shape-arm cells are unchanged. - scope-capture (rust): corpus growth only. emitRustScopeCaptures is untouched, and parking the new fixture reproduces the prior fingerprint byte-for-byte; it adds 131 capture groups and 3 fixture entries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(scope): ask resolveQualifiedCallee about dotted callee paths A dotted call-return binding (`x -> com.acme.mod.make`) whose head no namespace import binds came back from followChainPostFinalize untouched. The dotted branch tried only namespaceCallReturnType, which needs a two-segment path headed by a namespace import. The inline `com.acme.mod.make().m()` failed the same way in the compound resolver: once the object part resolved to no class and named no namespace import, the call branch returned nothing. A language whose package paths are dotted and never imported (Kotlin) could type neither form. Both sites now pass the dotted text to the provider's resolveQualifiedCallee after their own lookups miss, as they already did for undotted callees, and read the returned callable's return annotation by definition identity. The contract documents the two new consult points, and says that a provider must refuse a path whose head the call site can see as a value or type. No provider answers dotted text yet: Rust's hook reads only `::` paths. No edge moves, and the Rust, Python, Dart and Zig resolver suites pass unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(kotlin): type locals from package-qualified factory calls `val x = kqfmod.make(); x.m()` left `x` untyped although `make` is declared `: C`. `com.acme.mod.make()` failed the same way, and the inline `kqfmod.make().m()` dropped. Three gaps lined up: - the constructor-inferred query matches only a bare callee; - the navigation-call inference handles only typed locals and same-file classes; - nothing resolved a package name, because Kotlin imports declarations, not packages, so no import edge names `kqfmod`. The named-import form (`import kqfmod.make; make()`) already worked. - The local-assignment synthesis binds `x` to the whole callee path when the callee is a dotted path of plain names. The head must be lower-case and must not be declared or imported in the file as a value or type. A `@type.qualified-callee` marker stops interpretKotlinTypeBinding from reducing the path to the function name. Typed-local and same-file-class receivers (`repo.find()`, `Logger.create()`) are tried first, so they keep their meaning. - New Kotlin resolveQualifiedCallee. The package is everything before the last segment. A package spans files, and across them it must declare exactly one top-level function with that name. The index of package facts and module bindings is built once per pass. The hook refuses overloads. It also refuses a head the call site can see: anything the lexical claim walk finds, plus top-level names in same-package sibling files and star-imported packages, which that walk misses. - An untyped package function (`val x = kqfmod.untyped()`) now records a receiver-unresolved drop instead of an exact miss. New fixture kotlin-package-qualified-factory covers: - single- and multi-segment packages, and inline calls; - the untyped drop and the named-import control; - local, parameter, property, import, star-import and same-package values named like the package; - overloads, and companion/object member calls. A full lang-resolution A/B against the parent emits identical CALLS/ACCESSES edges and drops outside the new fixture. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(bench): rebaseline kotlin captures and receiver counts - receiver-resolution: the new kotlin-package-qualified-factory fixture records six drops, all in app/Callers.kt. Two are call drops on untyped receivers: `x.m()` on `val x = kqfmod.untyped()` and the inline `kqfmod.untyped().m()`. The other four come from `com.kqfacme.deep.make()` in deepCall and inlineDeep: each is a call drop plus a read drop on the `deep` receiver path. Call drops 154 -> 158, reads 50 -> 52, all kinds 224 -> 230. A same-corpus A/B shows no other drop change. Shape-arm cells are unchanged. - scope-capture (kotlin): moving the new fixture out of the corpus reproduces the prior fingerprint byte-for-byte. Base and new emitters differ by exactly six matches over the same corpus, all of them in the new fixture. Synthetic counts are unchanged, and scaling is 1.039 < 1.5. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(scope): reuse qualified call file sets * docs(scope): clarify finalized type refinement * test(kotlin): sync measured capture counts * fix(rust): scope generic alias parameters * fix(scope): respect base candidate visibility * test(scope): preserve library member collision hedge * fix(scope): cover more library receiver evidence * fix(vue): classify imported runtime receivers * fix(scope): match written JVM base arity * fix(kotlin): refuse unproven package receivers * fix(kotlin): prove companion receiver headers * test(kotlin): complete JVM scope fixtures * fix(kotlin): unify package receiver shadow checks --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c30449394b
|
fix(scope): bind same-package inheritance bases when the simple name is reused elsewhere (#3659)
* fix(scope): bind same-namespace inheritance bases before sibling visibility exists Cause: same-package / same-namespace visibility (`populateNamespaceSiblings`) is populated after the inheritance pre-pass and `buildMro`. A base written without an import (`class C extends B`, B in another file of the package) could bind only through the workspace-unique simple-name fallback, which any other package declaring a `B` defeats: no EXTENDS edge, an empty MRO, and lost calls through inherited members. In the combined lang-resolution corpus `java-grandparent-resolution` `c.greet().save()` dropped because `java-unrelated-method-collision` declares default-package A and B. Moving the sibling hook ahead of the pre-pass was measured and rejected. It retargeted correct edges and minted wrong ones, because the sibling channels carry candidates the pre-pass must not see: C# global-namespace types are consulted at every scope (same-file `Cat : Animal` bound another fixture's global Animal), namespace buckets merge across source roots with no proximity order (PHP, C#), and a qualified `extends app.base.Box` bound through a same-package `app.Box`. Fix: an optional `ScopeResolver.resolveImplicitInheritanceBase`, consulted by the pre-pass only when the shared chain bound nothing, the base was written bare (no qualifier, name capture at the start of the base expression), and no import claims the name. It adds edges and never retargets one. `resolveSameNamespaceInheritanceBase` picks the candidate in the deriving class's namespace, nearest by shared path prefix, refusing a tie. Java and Kotlin answer from their package facts, C# from the exact `namespacePrefix`. Corpus A/B (main vs this, same corpus): EXTENDS/IMPLEMENTS +42, all within their own fixture, 0 removed. CALLS: six `super.save()` edges keep their target but are now MRO-dispatched (`global`, as in isolation), one moves off another fixture's BaseModel.Save onto its own, and 7 are new (both grandparent `greet` calls, a default-method `validate`, the new fixtures). Receiver bench: call drops 128 -> 127, all kinds 192 -> 191, shape arm unchanged. Scope-capture fingerprints re-baselined for the added fixtures only (parked, --check passes on the old ones). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(java): resolve a duplicated package+type import to the importer's own root Cause: `buildJavaPackageIndex` kept the first file declaring each package+type. One repository can hold several build modules or sample projects that each declare `models.User`, so every `import models.User;` bound whichever copy was parsed first, a wrong IMPORTS edge at full confidence that carries the receiver types behind it. In the combined lang-resolution corpus 24 fixtures declare `models.User`, and e.g. `java-heritage` UserService.processUser called `java-assignment-chain/models/User.save`. Fix: the index keeps every declaring file in a sparse `duplicateTypes` map (only colliding pairs, so a normal workspace pays nothing), and a type import resolves to the declaring file sharing the longest leading path with the importer (its own module or source root). A tie, or an unknown importer, refuses rather than guessing. Wildcard imports are unchanged. Corpus A/B (previous commit vs this, same corpus): 40 CALLS edges that crossed into another fixture's User/Repo/Address/UserService are gone, 55 CALLS/ACCESSES edges now land in their own fixture (0 cross-fixture), and `java-parent-resolution` User IMPLEMENTS Serializable moves off `java-heritage` onto its own interface. Receiver-unresolved drops unchanged; import-target and receiver benches pass. Scope-capture java fingerprint re-baselined for the added fixture only (parked, --check passes on the old one). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(scope): respect base candidate visibility * fix(scope): match written JVM base arity --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
169aa5d960
|
fix(scope): label library-rooted receiver drops as external from provider evidence (#3658)
* refactor(scope): expose the position a receiver-chain fold reaches
`foldReceiverChain` answers "which class", so a chain whose last step is
declared as a type the program does not contain (`Dictionary<string,
User>`, `String`) reads only as a miss. That miss is informative to the
receiver-origin classifier: every hop was typed by a program declaration
and the last one names a library type.
Extract the fold loop into `foldReceiverChainState` and export
`foldReceiverChainPosition`, which returns the final position including
one whose declared type named no class, and reports nothing to
`recordReceiverType`. `foldReceiverChain` keeps its behaviour: it now
wraps the same loop and makes the receiver-type report from the final
state, as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(scope): classify library-rooted receiver drops from provider evidence
`classifyReceiverOrigin` had one source of positive external evidence,
`LanguageProvider.isBuiltInName`. That set also filters callee names out
of the call graph, so it could not grow to cover host globals, stdlib
imports or library-typed values without dropping real edges, and every
such drop hedged `impact()` to `lower-bound`. It also read a base name as
evidence even where the program bound that name itself: a parameter
named `console` with an unresolved type, or an unresolved import of a
built-in spelling from the program's own module, came back `external`.
Add two provider data hooks read only by the classifier:
`externalReceiverNames` (globals, platform roots, library types) and
`isExternalModuleSpecifier` (import specifiers reserved for the runtime
or stdlib). The classifier now answers `external` when:
- the base is unbound at the site and is a built-in or listed name;
- the base is bound only by imports that resolved to no program file and
all name a library module;
- the receiver chain folds, through declarations each owner makes, to a
position declared as a library type (`this.data.Keys` with
`Dictionary<string, User> data`), and the program declares nothing by
the dropped member's name (an extension method or trait impl could be
the real target). The fold skips ancestor-hoisted bindings, which can
lend another class's member type to the step.
A base the program binds (a parameter, a local, a resolved import, an
import from a non-library module) never takes a name-based verdict. The
in-program arms are unchanged and still win first. No drop is added or
removed and no edge changes.
Java supplies its reserved `java` package root for both hooks: the JVM
refuses to define classes under `java.*`, so `java.util.Optional.of(...)`
and `import java.util.Map` are rooted in the platform. Without the
specifier hook, the stricter import rule would have demoted the existing
`import java.util.List; List.of(...)` verdict to unknown.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(typescript): name JavaScript host globals as the program boundary
`document.body.appendChild(...)`, `window.history.pushState(...)` and
`Buffer.from(raw).toString()` were recorded `unknown`: none of
`document`, `window`, `Buffer`, `process` or `globalThis` is in
`BUILT_INS`, and they cannot be added there because that set also
filters callee names, so `process` would drop every call to a program
function named `process`. Each drop hedged `impact()` to `lower-bound`
although no program node could have been the target.
List the host objects in `EXTERNAL_RECEIVER_NAMES` and supply it to the
TypeScript, JavaScript and Vue providers as `externalReceiverNames`. A
name counts only where the program binds nothing by it and declares
nothing by it, so a `document: Doc` parameter, an untyped `window`
parameter, a local `navigator` and a program `class Buffer` keep their
verdicts. `location`, `history`, `self` and `crypto` stay out: they are
ordinary local names.
Corpus: 5 call drops move from unknown to external. `process.env.*`
(3 reads) stays in-program there because three fixtures declare a
top-level `function process()`, one of them in a global script.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(python): name standard-library imports as the program boundary
`os.environ.setdefault(...)` after `import os`, and
`Path(__file__).resolve()` after `from pathlib import Path`, were
recorded `unknown`. The imports resolve to no file in the program, but an
unresolved import alone cannot be told apart from an in-program module
the resolver missed, and nothing said that `os` and `pathlib` belong to
the interpreter.
Supply `isExternalModuleSpecifier` from a curated set of standard-library
modules, matched on the specifier's first segment (`os.path` -> `os`).
A relative specifier never matches. The classifier still requires the
import to have resolved to no program file, so a local `os.py` that the
resolver finds wins, and a parameter named `os` is the program's own.
Generic nouns an application plausibly uses for its own modules
(`types`, `queue`, `signal`, `secrets`, `string`, `email`, `http`, ...)
are left out. Third-party packages are not listed: an unresolved
`fastapi` import stays `unknown`.
Corpus: 4 call drops move from unknown to external
(`os.environ.setdefault` x3, `Path(__file__).resolve()`).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(zig): name @import("std") and @import("builtin") as the program boundary
`std.sort.pdq(...)` and `const Allocator = std.mem.Allocator;` were
recorded `unknown`: `std` is bound by `const std = @import("std")`, an
import the resolver deliberately leaves external, and nothing told the
classifier that the module is the standard library rather than an
in-program file it failed to find.
Supply `isExternalModuleSpecifier` for the two module names Zig
reserves: `std` and the compiler-generated `builtin`. Exact names only,
so `@import("std.zig")` (a file path) stays `unknown`, as does `root`
(the program's own root module) and any module `build.zig` names.
Corpus: 9 drops move from unknown to external: `std.sort.pdq` (2 calls,
2 reads), `std.mem.Allocator` (1 read), and the
`zig-qualified-literal` literals `std.Thread.Mutex{}` and
`std.mem.Allocator{...}` (2 calls, 2 reads). The last four are that
fixture's edge negative control (`useExternal` must emit no edge, which
still holds); their receiver is std, so `external` is the right origin.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(csharp): name the System namespace and Dictionary as the program boundary
`System.Console.WriteLine(...)` was recorded `unknown`: `Console` is in
`BUILT_INS` but the chain base is `System`, which is not, and `this.data.Keys`
with `Dictionary<string, User> data` was recorded `in-program` because
the base `this` resolves to a program class even though the member lives
on a BCL type.
Supply `externalReceiverNames` with `System` (the BCL root namespace),
`Dictionary` (the one collection capture does not reduce to its element,
because it takes two type arguments) and `string` (a keyword alias that
no program can declare). The base check covers `System.Console`; the
chain fold reaches `Dictionary<string, User>` through the program's own
field declaration. A program `class System`, a field of a program type,
and an extension method the program declares on `Dictionary` all keep
their verdicts.
Corpus: 3 call drops move from unknown to external
(`System.Console.WriteLine`). The two `this.data.Keys/Values` reads in
`csharp-nested-member-foreach` stay in-program there: the fixture's
global-namespace `App` is one of several in the merged corpus, so `this`
binds another fixture's `App` and the fold declines. Analysed alone,
both read `external`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(kotlin): name java.lang.System and the platform roots as the program boundary
`System.out.println(...)` in Kotlin was recorded `unknown`: Kotlin's
`BUILT_INS` lists stdlib function names, not `System`, which Kotlin/JVM
imports by default from `java.lang`.
Supply `externalReceiverNames` with `System` and the package roots no
program can declare into (`java`, refused by the JVM; `kotlin`, reserved
by the compiler for the standard library), and `isExternalModuleSpecifier`
for imports under those roots. A program `object System` keeps the
receiver `in-program`.
Corpus: 1 call drop moves from unknown to external
(`kotlin-object-literal-scope` `System.out.println`, whose decoy
`println` on the enclosing object still gets no edge).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(bench): rebaseline receiver-origin buckets for library receivers
The preceding commits label library- and runtime-rooted drops `external`
instead of `unknown`. Call drops by origin move from 55 in-program, 44
external and 29 unknown to 55, 62 and 11. Total drops (192), call drops
(128), every per-extension and per-shape count, and every shape-arm
state are unchanged; a same-corpus A/B against main keeps all 2,838
CALLS/ACCESSES edges byte-identical.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(scope): preserve library member collision hedge
* fix(scope): cover more library receiver evidence
* fix(vue): classify imported runtime receivers
---------
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
65b101407e
|
fix(rust): type locals from module-qualified factory calls (#3657)
* fix(scope): follow namespace factory return types and record untyped drops `import mod as rp; x = rp.make(); x.m()` lost the receiver type even when `make` is declared `-> C`: `x` was bound to the raw dotted name `rp.make`, and nothing followed a dotted name to a function's return type. The inline `rp.make().m()` failed the same way in the compound resolver, which read `ns.f()` only as a construction. Resolve `ns.f` to `f`'s return annotation when `ns` is a namespace import, the fact `propagateImportedReturnTypes` already mirrors for a named import. The post-finalize chain follow uses it for bindings and the compound resolver for inline calls. Only sites that emitted nothing change: the full lang-resolution corpus emits the identical 2,838 CALLS/ACCESSES edges. Record the drops that left `impact` at "exact": - a plain receiver bound to a namespace function with no usable return type (`x = ns.untyped()`); - an outer call whose `file:line:col` key a resolved inner site already claimed (`ns.untyped().m()`, `s.repo.Delete(id)`). Only a duplicate capture of the same member hides a drop now. Receiver baseline: call drops 128 -> 151, reads 44 -> 48, ten shape-arm cells INVISIBLE-GAP -> VISIBLE-GAP; every new drop has no CALLS edge to its member (BASELINE.md lists them). The python.factory-qualified accuracy gap is removed; the python-scope fingerprint and capture golden gain the new fixture only. Fixes #3498 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(dart): type locals from prefixed-import factory calls `import 'mod.dart' as rp; final x = rp.make(); x.m()` gave `x` no type: local call inference accepted only a bare callee (`Callee()`), and the prefix-stripping type normalizer would have reduced `rp.make` to `make`. Bind a local initialized by exactly `prefix.member(...)` (direct or awaited) to the dotted callee and keep its prefix, so the shared chain follow types it from the namespace function's return annotation. Only call-derived bindings keep the prefix; annotated `prefix.Type` spellings are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(zig): type locals from namespace-module factory calls `const rp = @import("mod.zig"); const x = rp.make(); x.m()` bound `x` to the namespace handle `rp` itself: the call-return rule reads a non-value head as the type being constructed (`Counter.init()`), and import bindings are not values. The member name was gone before the shared chain follow could use the new namespace return-type lookup. For a module-level handle on a lowercase file (a namespace module, by Zig convention), bind the dotted callee `rp.make` so the shared chain follow types it from `make`'s return annotation. File-struct handles (`@import("Page.zig")`, any spelling of the handle) keep naming the struct, so `Page.init()` is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(bench): rebaseline captures and receiver counts for Dart and Zig - receiver-resolution: the Dart fixture's untyped factory adds one call drop (151 -> 152); every typed form in the Dart and Zig fixtures resolves. - scope-capture (dart): two existing dart-method-enrichment locals initialized by `obj.method()` / `Type.method()` gain a call-derived binding, plus the new fixture's captures. A full lang-resolution A/B emits the identical edges outside the new fixtures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(rust): type locals from module-qualified factory calls `mod modx; use crate::modx as rp; let x = rp::make(); x.m()` left `x` untyped although `make` is declared `-> C`. The call-return rule kept only the text before the first `::` (the `Foo::new()` idiom), so `x` was bound to the module `rp` and the function name was lost. `modx::make()` and `crate::modx::make()` failed the same way, and the inline `rp::make().m()` failed in the compound resolver, which cannot split a `::` path. With a `let modx = Other;` in scope, `modx::make()` even typed `x` as `Other` and emitted a wrong `Other.m` edge. - Rust keeps the whole callee path when every segment is lower-case: a module function, by rustc's lint-enforced casing. `Foo::new()` and `Self::new()` still read as the type. - New `ScopeResolver.resolveQualifiedCallee` hook. The shared chain follow, the compound call branch and the drop recorder hand a callee text no scope binds to the provider, which returns the callable it names; the shared side reads that callable's return annotation by definition identity and never splits the path. Rust answers with the module-tree walk behind `resolveQualifiedFreeCall`, so a call's type and its CALLS edge come from the same resolution. - A receiver bound to an in-program module function with no return annotation (`x = rp::untyped()`) records a `receiver-unresolved` drop, as #3498 does for `ns.untyped()`. - `rp::make()` did not resolve as a call either: the module-name filter rejected the alias `rp`, and under Cargo the import binds the module's `mod modx;` declaration rather than `modx.rs`. The `use` candidate now admits an alias that names a module and reads a module from its top-level declaration. A full lang-resolution A/B against the parent emits identical CALLS and ACCESSES edges outside the new fixture, except one new correct edge: `rust-ambiguous` `main -> models::Handler.handle`, typed from `let h = services::create_handler()`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(bench): rebaseline rust captures and receiver counts - receiver-resolution: the new rust-qualified-factory-return fixture's two untyped sites (`x = rp::untyped(); x.m()` and inline `modx::untyped().m()`) are each a call and a callee-position read drop: call drops 152 -> 154, reads 48 -> 50, all kinds 220 -> 224. No other drop moves in a same-corpus A/B, and shape-arm cells are unchanged. - scope-capture (rust): corpus growth only. emitRustScopeCaptures is untouched, and parking the new fixture reproduces the prior fingerprint byte-for-byte; it adds 131 capture groups and 3 fixture entries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(scope): reuse qualified call file sets * docs(scope): clarify finalized type refinement * fix(rust): scope generic alias parameters --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a09f76be39
|
fix(cpp): type receivers declared or produced through qualified names (#3656)
* fix(cpp): resolve qualified receiver types by namespace, not by tail
Cause: a C++ receiver declared with a qualified type (`rp::C x = ...`,
`void f(a::Config c)`, `rp::Box<int> b`) typed nothing. The capture keeps
the qualifier, but the shared receiver lookups read a qualifier only as a
`.` namespace-import handle, and C++ has neither: `#include` binds no
handle and the index keys `rp::C` under `C`. The two places that did type
qualified spellings — the `auto x = ns::T{}` brace-init rule and qualified
member fields — reduced them to their tail, which binds the NEAREST
same-named class: `auto x = rp::C{}` reached a global `C` through another
include, and `a::Config` inside a `namespace b` declaring its own Config
reached `b::Config` (both confident wrong CALLS edges).
Fix: a ScopeResolver opt-in, `resolveQualifiedTypeSpellings`, that C++
sets. When a qualified declared or return type binds nothing,
`resolveClassBindingForName` and the compound resolver's return-type lookup
fall back to `resolveQualifiedInheritanceBase` — the route qualified base
classes and qualified type references already take: the qualifier is
matched against the candidate's namespace and a tie resolves nothing. It
runs after every existing lookup and after the template-argument match, so
it adds edges and never retargets one. The brace-init rule now captures the
whole qualified_identifier, and non-generic qualified member fields keep
their qualifier, so both resolve through the same route; generic members
stay tail-reduced because specialization matching keys on the bare base
name.
Corpus A/B (lang-resolution, all languages): no edge outside the new
fixture moved; drops unchanged. bench/scope-capture cpp re-baselined (one
brace-init capture text, plus the new fixture).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cpp): bind qualified declarations that have no initializer
Cause: `rp::C x;` and `rp::C* x = nullptr;` emitted no type binding at
all. The plain-declaration annotation rules accept only a bare
`type_identifier` or `template_type`, and tree-sitter-cpp parses a
qualified type as a `qualified_identifier` wrapping one of those, so the
receiver of `x.m()` was untyped (no edge, and no drop either). The
initialized form `rp::C x = ...` was already captured by the `type: (_)`
assignment rule.
Fix: two @type-binding.annotation rules on the OUTER qualified_identifier
(plain declarator, and pointer inside an init_declarator) — the qualified
twins of the bare rules, one pattern per declarator shape like the
qualified member-field rules, with no node-type alternation. The spelling
is kept as written and resolved by the qualifier-aware route from the
previous commit.
Capture diff over the cpp corpus: 29 added @type-binding.annotation
matches on qualified locals, nothing removed or changed. Corpus A/B: no
edge or drop outside the new fixture moved. bench/scope-capture cpp
re-baselined.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cpp): record return types of header function prototypes
Cause: only function DEFINITIONS (and in-class method prototypes)
recorded a return type. A free-function prototype — `D makeD();`,
`namespace rp { C make(); }` — recorded none, and the usual layout is
"header declares, .cpp defines": the .cpp is never included, so an
includer saw no return fact at all and `auto x = makeD(); x.m()` /
`makeD().m()` typed nothing (the inline form recorded a drop, the bound
form not even that).
Fix: @type-binding.return rules for a prototype with a plain or pointer
return. In a function body the same syntax declares a block-scope
function (the most vexing parse, `D x();`), whose binding would be
hoisted to the module and type every undeclared `x` in the file, so
captures.ts keeps a prototype match only at namespace scope — the
translation unit or a namespace / extern "C" body, through preprocessor
conditionals (include guards), single-declaration extern "C" and
template prefixes. A prototype declared in a namespace keeps its binding
in that namespace instead of the module: the binding's scope is also
where its type is looked up, and from the module a global `Widget` in
the same header won over `rp::Widget`. Callers read namespace members by
definition identity, which joins in any scope, and namespace members are
never mirrored to includers by bare name anyway.
A reference return (`D& f();`) gets no rule: such a function is not
declared as a Function at all (no declaration pattern), so a return fact
would have nothing to attach to.
Capture diff over the cpp corpus: one added match outside the new
fixture (cpp-return-type's header prototype, same type as its
definition); the gate drops only the fixture's deliberate `E y();`.
Corpus A/B: no edge or drop outside the fixture moved.
bench/scope-capture cpp re-baselined.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(scope): type an inline call receiver from its callee's own return type
Cause: the compound resolver's free-call branch (`make().m()`) read the
callee's return type BY NAME from the caller's scope chain, while the
chain follow that types the bound form (`auto x = make(); x.m()`) reads
it BY DEFINITION IDENTITY (`producerTypeRef`). A callee that arrives by a
channel nothing mirrors a return binding through — a C++ using-directive
— therefore typed the bound form and dropped the inline one.
Fix: when the name read finds nothing, fall back to the return type the
callee's definition declares, via `producerTypeRef` (now exported from
imported-return-types.ts; the only change there). Only for a callee the
name actually BINDS at the call: `findExportedDefByName` falls back to the
first workspace callable of that name, and reading a return type off that
guess is not evidence. Measured: without the binding check the corpus
gained 15 non-C++ edges (Go range/chain factories, Swift call results),
all reached through that workspace-name guess and one of them provably
wrong (swift-return-type's `user.save()` bound to another fixture's
User); with it, no edge outside the new fixture moves.
Corpus A/B: edges and drops outside the fixture identical to main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cpp): type receivers produced by a qualified factory callee
Cause: `auto x = rp::make(); x.m()` bound `x` to the literal type `auto`
— the bare `auto x = f()` constructor rule matches only an identifier
callee, so the generic assignment rule captured the placeholder — and
`rp::make().m()` reached the compound resolver as the free call
`rp::make`, whose name binds nothing. Neither had any route from the
qualified callee to its definition, so both typed nothing (`auto x =
rp::C()` likewise).
Fix:
- captures.ts promotes `auto x = ns::f()` / `auto x = ns::T()` (a
qualified_identifier whose innermost name is a plain identifier) to
@type-binding.constructor with the whole callee as the type, the
qualified twin of the bare rule; template factories keep their own
rule. interpret.ts drops any leftover `auto` / `decltype(auto)` binding,
which named no type.
- New ScopeResolver hook `splitQualifiedCallee` maps a callee spelling onto
the receiver/member pair the language's qualified CALL captures use (C++:
innermost scope segment, `a:🅱️:make` -> `b` / `make`), so shared code
never splits on `::`. The compound resolver's free-call branch, when the
callee's name binds nothing, resolves the definition through the existing
`resolveQualifiedReceiverMember` hook — the answer the call itself got —
and reads that definition's own return type by identity
(`producerTypeRef`). `auto x = rp::C()` resolves through the
qualified-type route instead.
Capture diff over the cpp corpus: one changed match outside the new
fixture (cpp-method-enrichment `auto category = Animal::classify(...)`
now binds the callee instead of `auto`; a class static method, which the
namespace lookup does not answer, so it stays untyped as before). Corpus
A/B: no edge or drop outside the fixture moved. bench/scope-capture cpp
re-baselined. The new cpp-captures rows also pin the namespace-scope gate
on prototype return types from the earlier commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cpp): bind qualified receiver owners
Preserve complete namespace qualifiers and declaration-owned return facts. Resolve inline namespace and chained alias types without borrowing unrelated functions or classes; cover review collision and header-order cases.
* fix(cpp): honor qualified type visibility
Carry header alias activation positions through include chains. Refuse unresolved type-owned qualifiers without rejecting value names, and align the lexical-ownership fixture with the scope-index contract.
* style(cpp): format inline namespace lookup
* fix(cpp): preserve factory lookup position
Keep later local type declarations from hiding an earlier namespace
factory. Name overload and class-factory recall limits explicitly.
Refs #3656
* fix(cpp): expand factory return aliases
Apply the provider qualified-type hook in producer-return lookup.
Keep authoritative refusals and the original declaration context.
Refs #3656
---------
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
caacc3bac2
|
fix(csharp): rank global-namespace types below the file's own namespaces (#3655)
* fix(csharp): rank global-namespace types below the file's own namespaces C# global-namespace types were stored in the scope-independent workspaceFqnBindings channel. lookupNameClaim returns that channel at every scope, while namespace siblings are consulted only at the module scope. A type name looked up inside a method therefore stopped at the first global type with that name. The walk never reached the file's own or same-namespace declaration. On the merged lang-resolution corpus this caused four field-chain drops in csharp-field-types and csharp-deep-field-chain. It also bound calls to other fixtures' global types. For example, csharp-hello's Demo.Greeter.Main called csharp-optional-params' Greeter. The C# hook now stores global types in namespaceFqnBindings under the '' key. Each file's accessibility list follows C# name lookup: the declared namespace, the using targets of that declaration, the enclosing namespaces, then the global namespace. The compilation unit's using targets come after it. A using after a file-scoped namespace belongs to that namespace. Shared walker logic is unchanged (comments only); PHP's FQN keys still use workspaceFqnBindings. Corpus A/B against main: the four drops resolve and no drop is added. Wrong cross-fixture edges are retargeted or removed. One global-namespace fixture now types `this` as its own class and reaches a duplicated global User. Receiver bench call drops 128 -> 125, all-kind 192 -> 188 (baseline and BASELINE.md updated; the old note that these sites declined on ambiguity was wrong). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(csharp): initialize voucher fixture payee * test(csharp): refresh initialized payee captures --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2b44565534
|
fix(resolution): correct receiver drops and property ownership (#3653)
Some checks are pending
Gitleaks / gitleaks (push) Waiting to run
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
* fix(js): withdraw receiver drops for object-literal key writes a later pass answers
The JS capture makes the enclosing object literal the receiver of each key
it writes (`return { wickRatio: row.a }`). Case 0 of the receiver-bound pass
treats any receiver text holding `.` or `(` as a compound expression, and a
literal holding `row.a`, `0.5` or `...seed` does, so the fold failed and the
site was recorded as a `receiver-unresolved` drop. The return-shape and
unique-name passes run later and answered 17 of these 19 writes with an
ACCESSES edge, but nothing withdrew the earlier drop.
Both passes now report the sites they emitted an edge for, keyed by file,
position, site kind and member name (position alone is shared by `a.b` and
`a.b.c()`), and run.ts withdraws the matching `receiver-unresolved` drops
once they have run. A site they leave unanswered keeps its drop.
The other two writes, in `SignalService.make()`, had no edge at all: the
own-return-shape tier read the caller's owner qualifier as
`SignalService.make#0`, so it never matched the method's keys
(`SignalService.make.secretFlag`) and three same-named candidates were
refused. The qualifier is now the qualified name after `<Label>:<file>:`
without the `#<arity>` suffix. It also stops cutting a positioned id
(`outer.parse@25:2`) down to its column.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(js): type a static field from its initializer for class-level receivers
`Config.DEFAULT.validate()` with `static DEFAULT = new Config()` produced no
CALLS edge. JS and TS keep a static field's type binding out of the class
scope on purpose: the scope has one binding map for static and instance
members, so the static binding would retype an instance field of the same
name. The class-level receiver route (`declaredTypeOfClassLevelField`) reads
the static Property node instead, but only its annotation-derived
`declaredType`, and an unannotated field has none. The type had nowhere to
live.
The field extractor now records `initializerType` for a field that declares
no type and is initialized by `new X()` / `new ns.X()` (the same two shapes
the scope queries bind), the parse worker stamps it on the Property node, and
the class-level route falls back to it after `declaredType`. The binding still
never enters the shared class-scope map, so the instance-twin hazard does not
return.
This resolves one hop: `Host.q.m()` now reaches `Alien.m`. A longer chain off
the field still goes through the compound fold, which types a field step only
from the class-scope map, so it stays unresolved. The matrix known-gap row
`static-read-without-a-twin-loses-its-type` flips to a positive row for TS,
with a JS twin added.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(js): keep CommonJS module.exports declarations out of receiver drops
`module.exports.arrowRequest = (url) => fetch(url)` declares this module's
`arrowRequest` export; the CJS capture rules already bind it like
`exports.memberRequest = …`. The same line also yields a write reference
whose receiver is `module.exports`, and because that text has a dot the
receiver-bound pass folded it as a compound value, failed, and recorded the
declaration as a `receiver-unresolved` write. The bare `exports.x = …`
spelling never reached that path.
A new ScopeResolver hook, `isModuleExportAssignment`, lets a language say a
write site is a module-export declaration. The receiver-bound pass consults
it only at the drop guard, after every case has failed, so it records no
drop and changes nothing else: the site is not marked handled, no edge is
added, and the existing module-level write edge stays. The JS and TS
resolvers implement it for a receiver of exactly `module.exports` (from the
minted chain, or the text when no chain was minted) when `module` resolves
to nothing in scope. A `module` parameter, as in a UMD wrapper, or a write
into a value hanging off the export object still records its drop.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(ts): record an owned-`this` chain receiver as owned-but-unbound
`this.route.go()` inside an object-literal method was filed as a generic
`receiver-unresolved` drop (or, depending on site order, as nothing). The
method scope owns `this` and gives it no type, which the pass detects with
`isReceiverOwnedButUnbound`, but that check was handed the whole receiver
text `this.route`. No scope owns a chain, so the call fell through to Case 0,
whose fold cannot type an owned `this` either.
The check now receives the receiver's base (from the minted chain, else the
text before the first `.`/`(`), the same base the `suppressReceiverLookup`
check already used. The call is recorded as `receiver-owned-but-unbound`,
its real reason, and still emits no edge: the dynamic-this negative control
holds. On the fixture corpus this moves only that site; no edge changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(resolution): preserve receiver ownership
Bind a return key's declaration write by exact file and position so
unrelated input accesses keep their ordinary candidate ranking. Stop at
untyped static overrides and resolve constructor initializers as values.
Preserve the combined drop guards after rebasing onto main, cover the
binding regressions, and refresh the receiver baseline after #3654.
Key answered sites by their full range to retain repeated-name outer
reads. Keep owned-but-unbound calls in the persisted completeness signal.
* fix(resolution): address feedback (#3653)
Classify module-export declarations using captured module kind before
suppressing receiver drops. Match same-named field metadata by source
position, including declarations on one line.
Require reference kinds in suppression assertions and guard temporary
fixture cleanup. Cover the reported failures with regression tests.
* perf(parser): index repeated field declarations
Sort private field buckets once and use binary search to avoid
quadratic scans while preserving declaration disambiguation.
* fix(ingestion): preserve capture metadata
Record module kind from the parsed tree on worker and main-thread paths.
Match Ruby field metadata at the accessor call instead of its argument.
* fix(ingestion): harden capture metadata
Keep Ruby reader/writer properties writable. Walk module-kind ASTs iteratively so deep expressions cannot abort extraction.
* test(ingestion): clarify worker metadata scope
* docs(ingestion): state capture metadata effects
---------
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
d57f570dff
|
refactor(ingestion): colocate import resolution rules (#3670)
* refactor(ingestion): remove dormant import resolvers Remove the unused LanguageProvider importResolver contract, factory and strategy chain. Keep active scope resolver helpers and their semantic tests. Verified 5,309 corpus edges unchanged, 7,411 focused tests, typechecks, build and resolver benchmarks. Full local suite is still running; graph analysis remains partial on deleted and comment-only regions. * refactor: colocate language import helpers Keep import policy beside language resolvers and share only generic indexes. Pass the existing ordered candidates explicitly so lookup behavior and cache identities stay unchanged. * fix: defer MCP imports and narrow Python paths * fix(python): prefer packages in relative imports * test(python): pin package-first import lookup * test(python): use a valid parent-relative import --------- Co-authored-by: Gergely Magyar <magyargergo@users.noreply.github.com> Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> |