mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-11-04 07:27:20 +00:00
* 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 reverts5d06d731c. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(scenario-map): decide pairs past unknown mutants on a kill witness A mutant both tests ran cleanly, killed by assertion by exactly one, proves the kill sets differ whatever the unknown and runtime-class mutants hold. kills-unknown and non-assertion-outcome now leave such a pair Inconclusive no longer; it goes on to Row or Distinct. A pair's empty kill set only decides Weak on a fully known run. Verdict engine 2. Re-merged from the artifacts of run 40183982111: Inconclusive 4532 -> 1469; every pair engine 1 decided keeps its verdict, rule and details, and the repeat candidates are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(scenario-map): record a test file's import failure instead of a missing result A static mutant that breaks a module at import makes its test files fail to collect: the runner writes one file-level killed-outside-test:collection row and no per-test rows, so every test of the file was a missing result. Those were every missing-result observation of run 40183982111 (597 mutants, 675 same-file pairs left Inconclusive by them alone). Extraction now records each test of a failed file with that class. When both tests of a pair failed to import, neither ran, so the mutant cannot tell them apart: comparePair counts it as a non-kill for both rather than another class. Verdict engine 3. 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 again The engine 3 run on PR #3686 finished (run 40208819019, complete, 9 of 9 shards). This reverts0e76acc09. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(scenario-map): record the engine 3 run's results Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(scenario-map): scenario fingerprint verdicts (same path, input, expectation) Two tests are duplicates when they run the same execution path on the same input data and check the same expectation. The fingerprint engine decides that from Layer 1 block sets, Layer 2 facets and discarded values, the checks-and-context key and the trace run, with no mutation: duplicate, possible-duplicate (anything unresolved, fail-closed), table-row (other input values), different-checks, distinct. On run 40208819019 the fully resolved triple names 26 pairs: the run's 13 strong repeats and 13 more mutation could not decide, none contradicted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(scenario-map): lead the report with the scenario fingerprint The report (schema 2) carries every pair's fingerprint verdict with its continuity key and the run's mutation scope. The markdown and the tracking issue lead with duplicates and same-scenario-different-checks pairs; the mutation sections (strong repeats, weak tests, gaps) render only when the run gathered mutation evidence. The spot audit samples duplicates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(scenario-map): mutation scope (none, possible-duplicates, all) plan and all take --mutation. The plan computes every pair's fingerprint before the trace and packs only the scope's target: no group (none), every group (all), or the groups with a possible duplicate, trimmed to the tests of those pairs and the files they executed. Value flow re-plans a narrower scope after the trace (a usable trace only narrows it) and builds the PDG index for scope all only. Merge reports every pair's fingerprint and the mutation verdicts of the pairs whose tests ran in a mutated group. A plan with nothing to mutate has no shard; all skips the shard stage then. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(scenario-map): check fingerprint verdicts in the controls oracle expected.json gives every control its fingerprint verdict and rule, written from the fingerprint rules before any run: verbatim-duplicate is the one duplicate, enum-member a possible duplicate (residual difference), numeric-boundary distinct, the other 13 table rows. The controls stage compares both verdicts per pair and fails on an unlisted fingerprint duplicate. Local controls run: all 16 pairs match on both. value-flow checks for dist/ only when it builds the PDG index (scope all); controls, and all with scope all, still check before any stage. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ci(scenario-map): fingerprint weekly, mutation on dispatch or label The weekly run is the fingerprint only (mutation scope none: no build, no shards). Workflow dispatch takes a mutation input (none, possible-duplicates, all). Adding the scenario-map label to a pull request starts the deep run over the possible duplicates; it writes no issue and no baseline, and every run puts the issue-body view in the job summary. Pull request runs get their own concurrency slot. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(scenario-map): the scenario fingerprint as the primary verdict The design defines a duplicate as the same execution path, input data and expectation, records the maintainer decisions of 2026-10-25 (fingerprint weekly, mutation on dispatch or label) and the local measurement: the fingerprint-only run took 8.2 min over all 4,632 eligible tests and named the 26 duplicates the evidence predicted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ci): place a single shard artifact in the scenario-map merge download-artifact unpacks a pattern that matches one artifact into the path itself, not into a directory named after it. With a one-shard plan (the scenario-map label's possible-duplicates run) the placement step found no shard directory, so merge reported the shard missing and every mutated pair Inconclusive (run 40268120818). The step now uses the download path when no artifact directory exists. A local re-merge of that run's artifacts gives the complete result, recorded in the design doc. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(scenario-map): fingerprint every unit and integration test The fingerprint needs no Stryker run, so its scope is no longer the mutation runner's validated envelope. Eligibility now carries fingerprintFiles (every *.test.ts under test/unit and test/integration) next to the mutation-eligible files; a verdict input carries eligible (mutation), fingerprintEligible and passed. - Layer 1 and the trace run the base config's lbug-db files in a partition of their own with one worker (partitions.ts), the rest with --concurrency workers, and merge both runs. - New fingerprint rule not-passing: a test that did not pass in Layer 1 makes its pairs Inconclusive. - The mutation target keeps only pairs whose two tests are mutation-eligible. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(scenario-map): run Layer 1 in CI slices; load vendor and dist in integration runs A sample of the integration tests showed two problems with fingerprinting every unit and integration test: - Half the sampled files failed to load: the prepare step (gate-3 hashed) leaves vendor/ and dist/ out of the prepared copy, and integration tests load leiden, the parse worker and the built CLI. Layer 1 and the trace now copy both from the checkout in normal mode, and every stage that runs tests (coverage, plan without --coverage, value-flow, all, controls) requires a built dist/. - One plan job cannot hold Layer 1: 44 of 435 integration files ran past 35 minutes. A new coverage stage (--index i --of n) runs one slice, split like the CI test shards (test/helpers/shard-balance.ts), and plan --coverage <dir> merges the slice files: one block table, indices remapped, files and tests in file order. A slice that did not report leaves its files unrecorded. The workflow runs 12 coverage slices before the plan. The sequential partition now reads every fileParallelism: false project of the base config (lbug-db and cli-e2e), not lbug-db alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(scenario-map): order-dependent controls pair is Inconclusive on the fingerprint The not-passing fingerprint rule (a test that did not pass in Layer 1 holds its pairs Inconclusive) came after expected.json was written. Layer 1 runs every test twice in a row (repeats 1, design §5.2); the order-dependent fixture's shared counter makes each test's second attempt fail, and Vitest reports a test failed when any repetition fails. So rule 2, not-passing, decides the pair before input-values-differ. The expectation follows from the rule and the fixture's code; the CI controls run that reported the difference is run 40278026899. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(scenario-map): skip Layer 1 for files no fingerprint finding can come from Past the path rule, a pair is a duplicate, a possible duplicate or a table row only when both checks-and-context keys are equal or one is unknown, and the key is static (facet/checks.ts over the static inventory). Layer 1 now runs only the files that hold a test whose key another test shares or that has none, plus every file the static view cannot vouch for: a call whose instance count is not static, a file snapshot read, a call at registration level that is not the test API (a helper such as defineRenameFixtureTests or describeGrammarPresence, or an it alias), or a parse failure. Mutation scope all and the controls still run every file; the coverage jobs get the run's --mutation so they slice the same files as the plan. On the current checkout this skips 449 of 1,399 test files, about 24% of the weighted Layer 1 cost. On the 250 unit files of the last full run, the static keys reproduce all 3,313 runtime keys and select the same 172 files; runtime collection (vitest list --no-static-parse) of the skipped files finds no test the static inventory misses. Given up: different-checks pairs with a test of a skipped file (0 on the last full unit run). The report says how many files Layer 1 ran. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(scenario-map): diff-scope the labelled pull request run A labelled pull request asks whether its own tests duplicate anything, yet the run fingerprinted the whole suite (12 coverage slices, hours of runner time). plan --changed-since <ref> now scopes it to the diff: - the changed test files are the fingerprint's files the diff since the merge base of ref and HEAD added or modified; - Layer 1 runs the changed files the prefilter keeps plus every file with a test whose checks-and-context key a changed test shares; - candidate pairs are kept only when they have a test of a changed file, before the groups form, so the mutation target, the trace and the report follow; - a diff that changes no test file writes an empty work file and an empty, complete report. The workflow skips the coverage matrix for pull requests and passes the merge commit's first parent (fetch-depth 2); the plan job builds dist/ and runs Layer 1 itself. The report names the changed files. Locally, over the two test files of the previous commit, the run took 4 min 41 s end to end (Layer 1 over 2 files 1.3 min, trace 1.1 min) on a loaded shared host. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(scenario-map): size Layer 1 at 4 coverage slices, not 12 Twelve slices came from an estimate of three times the CI coverage time, built on a local integration sample that other suites on the shared host had slowed down. The test workflow runs the whole unit and integration suite with coverage in three 7-8 minute shards (ci.yml run 30014110469), and an instrumented Layer 1 run shows its overhead is small: an integration file's work sits in beforeAll, which Layer 1 neither repeats nor snapshots per test (resolvers/rust: 10.1 s of coverage snapshots against about 190 s for the file). With each slice paying about five minutes of setup, twelve slices spent more on setup than on tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(scenario-map): report the Layer 1 tests that got no fingerprint Tests that Layer 1 saw but did not run dropped out of the candidates without a word: skipped ones (skip, skipIf, todo; the GITNEXUS_BENCH benchmark suites, which run only in the test workflow's benchmarks job) and unfinished ones (pending, because their file's run ended first). The report's eligibility section now counts both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(scenario-map): close gaps a code review found in the Layer 1 work - The registration guard let a suite whose callback is not written inline (describe('x', defineCases)) through, so a file whose tests the inventory cannot see could be skipped. Such a suite now keeps its file. - An empty coverage slice exited 2 and uploaded nothing; it now writes an empty slice file. - --changed-since was accepted with the controls fixture, whose run must cover every file; that is now a usage error. - The report counted the files of a coverage slice that did not report as run. It now says how many files wrote no Layer 1 record. - A diff that only changes a snapshot file scoped nothing; a changed __snapshots__/<name>.snap now counts as a change of its test file. - The diff-scope report claimed no other file can hold a changed test's duplicate. Files whose tests have no static key are not run, so it now says a possible duplicate there is not looked for. Not changed: mutation shards never get integration tests, because mutationTarget keeps only pairs of two mutation-eligible tests; a slice that disagrees with the plan stops the merge on purpose (the slices header now says so). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(scenario-map): cut setup cost and duplication in the Layer 1 work A four-angle cleanup review (reuse, simplification, efficiency, altitude) of the Layer 1 slices, prefilter and diff scope. Fixed along the way: a shard's row-deletion batch ran every planned pair of a complete group, including pairs whose tests the mutation target had trimmed away (an integration test, or a pair outside the scope); it now runs only pairs whose two tests the trimmed group holds (shardRowPlans). Setup cost: - Coverage jobs ran the Phase 0 classifier twice (about 33 s each) just to list the fingerprint's files; fingerprintScope lists them directly. - The plan recomputed the prefilter (about 30 s) to rebuild the slice split. Slice files now carry Layer 1's file list, the mutation scope and the revision; the plan checks those against its own run. - The prefilter re-parsed every test file. The unseen-registration check now lives in the static inventory (StaticInventory.incomplete), on the ASTs it holds and test-dupes' roleOf, which also catches test.describe with a named callback and it('x', namedFn). Simplification: runCoverage writes the empty work file itself; reuse partitionFiles, codeList, writeJson and path.matchesGlob (the hand-made glob matcher read ** differently from Vitest); sameList moves to util; copyBuildOutput owns the controls rule; slices are read before the work directory is emptied, so --coverage no longer has to lie outside --work; a dead sort is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(scenario-map): record the Layer 1 cleanup review in the fingerprint plan Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(scenario-map): full recall for diff-scoped pull request runs A labelled pull request's run left out unchanged files whose tests have no static checks-and-context key (helper-registered tests, dynamic it.each counts, file snapshots, no assertion key), so a changed test's possible duplicate in one was never looked for. Such a test can pair with any changed test, so the diff scope now runs every one of those files too. That floor is 230 of 1,407 test files, about 59% of the suite's weighted Layer 1 cost with 59 sequential files: too much for the plan job alone. A labelled pull request therefore runs the coverage slices as well: coverage --changed-since records the changed files in each slice, and the plan takes the diff scope from the slices (plan --changed-since stays for a local all run). The plan job no longer builds dist/ or fetches history. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ci(scenario-map): temporary owner-only test run on PR #3686 TEMPORARY, remove before merge. The scenario-map-test label starts the whole-suite run (no diff scope) with mutation possible-duplicates, so every stage (coverage slices, plan, value flow, mutation shards, merge) can be tested on CI before the workflow reaches the default branch, where workflow_dispatch would serve. It runs only when magyargergo adds the label on PR #3686; the label on any other pull request, or by anyone else, leaves every job skipped. The scenario-map label keeps its diff-scoped run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: stabilize scenario-map test selection * fix: exclude retry tests from scenario traces * fix: keep captured loop inputs unresolved --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
208 lines
7.9 KiB
JavaScript
208 lines
7.9 KiB
JavaScript
import tsPlugin from '@typescript-eslint/eslint-plugin';
|
|
import tsParser from '@typescript-eslint/parser';
|
|
import unusedImports from 'eslint-plugin-unused-imports';
|
|
import reactHooks from 'eslint-plugin-react-hooks';
|
|
import prettierConfig from 'eslint-config-prettier';
|
|
import requireSafeParse from './eslint-rules/require-safe-parse.mjs';
|
|
|
|
// Local plugin hosting custom rules that enforce GitNexus-specific invariants
|
|
// (currently: the Windows-SIGSEGV-safe parser entrypoint).
|
|
const gitnexusLocalPlugin = {
|
|
rules: {
|
|
'require-safe-parse': requireSafeParse,
|
|
},
|
|
};
|
|
|
|
// Selectors that protect MCP-reachable code from corrupting the JSON-RPC
|
|
// stdio frame stream. The MCP-reachable block below uses these directly;
|
|
// the lbug-adapter file-specific block must spread them in too because
|
|
// ESLint flat config REPLACES (not merges) `no-restricted-syntax` when
|
|
// multiple matching configs target the same file. Extracting to a const
|
|
// makes the dependency mechanical instead of documentation-enforced.
|
|
const mcpStdoutWriteSelectors = [
|
|
{
|
|
selector:
|
|
"MemberExpression[object.type='MemberExpression'][object.object.name='process'][object.property.name='stdout'][property.name='write']",
|
|
message:
|
|
'Direct process.stdout.write is forbidden in MCP-reachable code. Route diagnostics through console.error or process.stderr.write — the MCP stdio transport owns stdout for JSON-RPC frames.',
|
|
},
|
|
{
|
|
selector:
|
|
"CallExpression[callee.type='MemberExpression'][callee.object.type='MemberExpression'][callee.object.object.name='process'][callee.object.property.name='stdout'][callee.property.name='write']",
|
|
message:
|
|
'Direct process.stdout.write is forbidden in MCP-reachable code. Route diagnostics through console.error or process.stderr.write — the MCP stdio transport owns stdout for JSON-RPC frames.',
|
|
},
|
|
{
|
|
// Catches the canonical destructuring shape:
|
|
// const { write } = process.stdout;
|
|
// (and any other ObjectPattern destructure rooted at process.stdout)
|
|
// which would otherwise capture a reference to the original write
|
|
// and bypass the sentinel.
|
|
selector:
|
|
"VariableDeclarator[init.type='MemberExpression'][init.object.name='process'][init.property.name='stdout'] > ObjectPattern",
|
|
message:
|
|
'Destructuring process.stdout is forbidden in MCP-reachable code — bypasses the sentinel. Use process.stderr.write for diagnostics.',
|
|
},
|
|
];
|
|
|
|
export default [
|
|
// Global ignores
|
|
{
|
|
ignores: [
|
|
'**/dist/**',
|
|
'**/node_modules/**',
|
|
'**/coverage/**',
|
|
'**/.stryker-tmp/**',
|
|
'gitnexus/reports/mutation/**',
|
|
'gitnexus/vendor/**',
|
|
'gitnexus-web/src/vendor/**',
|
|
'gitnexus/test/fixtures/**',
|
|
'gitnexus-web/test/fixtures/**',
|
|
'gitnexus-web/playwright-report/**',
|
|
'gitnexus-web/test-results/**',
|
|
'**/*.d.ts',
|
|
'.claude/**',
|
|
'.history/**',
|
|
],
|
|
},
|
|
|
|
// Base TypeScript config for all packages
|
|
{
|
|
files: ['**/*.{ts,tsx}'],
|
|
languageOptions: {
|
|
parser: tsParser,
|
|
parserOptions: {
|
|
ecmaVersion: 2022,
|
|
sourceType: 'module',
|
|
},
|
|
},
|
|
plugins: {
|
|
'@typescript-eslint': tsPlugin,
|
|
'unused-imports': unusedImports,
|
|
},
|
|
rules: {
|
|
// Unused imports — auto-fixable
|
|
'unused-imports/no-unused-imports': 'error',
|
|
'unused-imports/no-unused-vars': [
|
|
'warn',
|
|
{ vars: 'all', varsIgnorePattern: '^_', args: 'after-used', argsIgnorePattern: '^_' },
|
|
],
|
|
|
|
// TypeScript quality
|
|
'@typescript-eslint/no-unused-vars': 'off', // handled by unused-imports plugin
|
|
'no-unused-vars': 'off', // handled by unused-imports plugin
|
|
'@typescript-eslint/no-explicit-any': 'warn',
|
|
'@typescript-eslint/no-non-null-assertion': 'warn',
|
|
|
|
// General quality
|
|
'no-debugger': 'error',
|
|
'prefer-const': 'error',
|
|
'no-var': 'error',
|
|
eqeqeq: ['error', 'always', { null: 'ignore' }],
|
|
},
|
|
},
|
|
|
|
// CLI/server packages — `console.log` IS the contract (CLI tool data output
|
|
// on stdout, e.g. `gitnexus query | jq`; server pretty-printed banners).
|
|
// Diagnostic logging (`warn`/`error`/`debug`/`info`) goes through pino like
|
|
// the rest of the codebase.
|
|
{
|
|
files: ['gitnexus/src/cli/**/*.ts', 'gitnexus/src/server/**/*.ts'],
|
|
rules: {
|
|
'no-console': ['error', { allow: ['log'] }],
|
|
},
|
|
},
|
|
|
|
// Forcing function for the pino migration. Severity is `error` — the
|
|
// codebase-wide migration is complete; new `console.*` in core source
|
|
// must fail lint. CLI/server are exempt above (legitimate stdout output).
|
|
// Tests, bin scripts, and the logger module itself remain exempt.
|
|
{
|
|
files: ['gitnexus/src/**/*.ts'],
|
|
ignores: ['gitnexus/src/cli/**', 'gitnexus/src/server/**', 'gitnexus/src/core/logger.ts'],
|
|
rules: {
|
|
'no-console': 'error',
|
|
},
|
|
},
|
|
|
|
// MCP-reachable code: forbid stdout-corrupting writes. The MCP stdio
|
|
// transport writes JSON-RPC frames to stdout; per the spec, the server
|
|
// MUST NOT write anything to stdout that is not a valid MCP message.
|
|
// Diagnostics must go to stderr (console.error). Direct process.stdout.write
|
|
// bypasses the gate and is also forbidden in these dirs.
|
|
// cli/mcp.ts is included here even though it lives under cli/ — it is the
|
|
// MCP entrypoint and inherits stricter discipline than the rest of cli/.
|
|
{
|
|
files: [
|
|
'gitnexus/src/mcp/**/*.ts',
|
|
'gitnexus/src/core/lbug/**/*.ts',
|
|
'gitnexus/src/core/embeddings/**/*.ts',
|
|
'gitnexus/src/core/tree-sitter/**/*.ts',
|
|
'gitnexus/src/cli/mcp.ts',
|
|
],
|
|
rules: {
|
|
'no-console': ['error', { allow: ['error'] }],
|
|
'no-restricted-syntax': ['error', ...mcpStdoutWriteSelectors],
|
|
},
|
|
},
|
|
|
|
// Windows SIGSEGV protection: every tree-sitter parse in `core/` must route
|
|
// through parseSourceSafe. Direct `<parser>.parse(content, ...)` crashes on
|
|
// Windows for inputs > 32 767 chars (V8 string-conversion bug, uncatchable
|
|
// from JS). The rule auto-fixes the call site; the developer adds the
|
|
// missing import after the fix runs. Out of scope: tests (skipped by the
|
|
// rule), the helper itself (`safe-parse.ts`), and the `grpc-patterns/proto.ts`
|
|
// grammar-load smoke test (filtered by string-literal-arg skip in the rule).
|
|
{
|
|
files: ['gitnexus/src/core/**/*.ts'],
|
|
plugins: {
|
|
gitnexus: gitnexusLocalPlugin,
|
|
},
|
|
rules: {
|
|
'gitnexus/require-safe-parse': 'error',
|
|
},
|
|
},
|
|
|
|
// React-specific rules for gitnexus-web
|
|
{
|
|
files: ['gitnexus-web/src/**/*.{ts,tsx}'],
|
|
plugins: {
|
|
'react-hooks': reactHooks,
|
|
},
|
|
rules: {
|
|
'react-hooks/rules-of-hooks': 'error',
|
|
'react-hooks/exhaustive-deps': 'warn',
|
|
},
|
|
},
|
|
|
|
// Prevent direct conn.close() / db.close() in the LadybugDB adapter (#1376).
|
|
// All close operations must go through safeClose() so the WAL is always
|
|
// flushed before the connection is released. The sole authorised call site
|
|
// inside safeClose itself uses an eslint-disable-next-line override.
|
|
//
|
|
// ESLint flat config REPLACES (not merges) `no-restricted-syntax` when
|
|
// multiple matching configs target the same file. lbug-adapter.ts is also
|
|
// covered by the MCP-reachable block above, so we spread the shared
|
|
// mcpStdoutWriteSelectors here alongside the safeClose selectors. Without
|
|
// this, lbug-adapter would silently lose its MCP stdout-write protection.
|
|
{
|
|
files: ['gitnexus/src/core/lbug/lbug-adapter.ts'],
|
|
rules: {
|
|
'no-restricted-syntax': [
|
|
'error',
|
|
...mcpStdoutWriteSelectors,
|
|
{
|
|
selector: "CallExpression[callee.object.name='conn'][callee.property.name='close']",
|
|
message: 'Use safeClose() instead of calling conn.close() directly (#1376).',
|
|
},
|
|
{
|
|
selector: "CallExpression[callee.object.name='db'][callee.property.name='close']",
|
|
message: 'Use safeClose() instead of calling db.close() directly (#1376).',
|
|
},
|
|
],
|
|
},
|
|
},
|
|
|
|
// Disable formatting rules (prettier handles those)
|
|
prettierConfig,
|
|
];
|