mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-01 02:01:24 +00:00
* fix(lbug): pin Ladybug core so Dependabot cannot ship a skewed FTS artifact The extension version is a separate upstream constant. Ignore daily core bumps and fail the pairing gate when the committed manifest does not name the installed core. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(lbug): make doctor and CI FTS gates resolve the packaged artifact Doctor and the REQUIRE_FTS file gates still treated an empty ~/.lbdb as unavailable, which would turn three CI jobs red once analyze stops installing into that tree. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(lbug): name native-abort and tuple-missing so analyze cannot mis-advise The CLI summary's trailing else treated every unknown skip reason as a missing extension. New crash and platform causes must get their own remedies, not a network-install hint. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(lbug): delete the dead read-path FTS index create ensureFTSIndex had no production callers and swallowed read-only CREATE_FTS_INDEX failures, which hid the only signal that a reader tried to write. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(lbug): vendor per-platform FTS artifacts so analyze needs no host install Keyword search depended on a CDN fetch into ~/.lbdb. Shipping the five published tuples inside the package makes air-gapped and ignore-scripts installs load the same artifact the publish gate checksums. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(lbug): load the packaged FTS artifact before any network install Analyze still required a CDN fetch into ~/.lbdb even when the package already shipped the file. FTS now path-loads the vendored tuple first and records source labels so a later truncated home copy cannot steal the diagnosis. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(lbug): diagnose a core/extension version skew instead of a missing runtime A structurally valid FTS artifact whose path version disagrees with the packaged pin must name both versions, not prescribe VC++ or OpenSSL. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(lbug): stamp an FTS phase so repair stays usable after an in-place abort A native CREATE_FTS_INDEX abort leaves no skip reason; the next run infers it from the dirty flag, and --repair-fts must not treat that phase as a half-written graph. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(lbug): park an in-place FTS crash WAL without wiping the graph An FTS abort after a successful checkpoint must reopen the live index on macOS, Windows, and Linux. Staging never parks the live WAL; readers keep today's large-WAL refusal. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(lbug): refuse read-only opens of an FTS-poisoned WAL MCP and serve cannot repair a leftover in-place abort. Fail before the native open and name --repair-fts, on macOS, Windows, and Linux. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(lbug): name a vendor-neutral Windows OpenSSL prerequisite OQ1 is unanswered here so GitNexus does not ship OpenSSL DLLs. Windows FTS now asks for a system OpenSSL 3 runtime instead of Git Bash PATH. Co-authored-by: Cursor <cursoragent@cursor.com> * test(lbug): inject the FTS vendor root and redact it on HTTP and MCP Path-loaded artifacts no longer vary with HOME. Tests pass an injected vendor tree and assert search warnings never leak a filesystem path. Co-authored-by: Cursor <cursoragent@cursor.com> * docs(lbug): document load-only as the global FTS install default Analyze still overrides to auto. Packaged per-platform artifacts load before any network install on macOS, Windows, and Linux. Co-authored-by: Cursor <cursoragent@cursor.com> * docs(lbug): format the FTS install-policy README table Prettier does not run on Markdown in pre-commit, so the U10 table wrap needs its own formatting commit. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(lbug): skip FTS CREATE after a persisted native abort A recovered analyze run was retrying CREATE_FTS_INDEX from skipReason alone. Keep that skip until --repair-fts, fail closed on unsupported tuples, and honor the checkpoint warrant for park/repair. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(lbug): honor checkpoint flushed warrant and align FTS tests with packaged vendor A no-op CHECKPOINT must not satisfy the FTS park warrant, and CI still asserted HOME-only FTS isolation after analyze started path-LOADing the packaged artifact. Co-authored-by: Cursor <cursoragent@cursor.com> * test(lbug): accept a nonempty incremental write set in the #2790 recovery check FTS-phase recovery can incremental-add files (changed=0, added=1). That is not the #2790 empty-diff wipe skip. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(lbug): compare FTS home versions to the core pin and tighten the publish filename gate Ladybug's ~/.lbdb/extension directory is the runtime/core version; treating it as the artifact version false-diagnosed skew. The publish guard now rejects a path-escaping filename the same way the fetch script does. Co-authored-by: Cursor <cursoragent@cursor.com> * test(lbug): seed FTS e2e fixtures from the packaged vendor artifact A machine with no ~/.lbdb copy should still run the vendor-survivorship cases; the seed no longer depends on HOME or a network install. Co-authored-by: Cursor <cursoragent@cursor.com> * Address PR review feedback (#3274) Keep in-place FTS abort evidence after persist so a second CREATE abort cannot fail-open readers, and close the CLI, loader, embed, and e2e gaps the review called out. Note: full npm test hit Ladybug worker-pool startup failures under memory pressure; tsc and 180 targeted unit tests passed. Co-authored-by: Cursor <cursoragent@cursor.com> * Address PR review feedback (#3274) Run the vendored-path symlink guard on the OS matrix, put e2e HOME fixtures on Ladybug's real extension layout, pin the embed crash-WAL gate before the writable open, and let analyze writers park through missing-shadow recovery. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(lbug): keep --repair-fts CI green after vendored-first FTS Never-installed warning fixtures must not inspect a packaged vendor binary, and a failed dirty restamp must not abort an otherwise successful --repair-fts run. Co-authored-by: Cursor <cursoragent@cursor.com> * test(cli): give the #1169 analyze e2e the same 90s Windows budget as its sibling The first #1169 persist-meta case was still on a 60s spawn/it budget and was killed banner-only on windows-latest after the FTS warning fixture no longer failed the shard first. Co-authored-by: Cursor <cursoragent@cursor.com> * test(ci): reweight Windows shards after the FTS e2e grew Vendored-first HOME fixtures pushed fts-extension-e2e to ~6 minutes on windows-latest, so the old 146s weight packed it with skills-e2e and blew the 20-minute watchdog. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
162 lines
8 KiB
TypeScript
162 lines
8 KiB
TypeScript
/**
|
|
* Weight-aware partitioning for the cross-platform test matrix.
|
|
*
|
|
* WHY THIS EXISTS. `run-cross-platform.ts` used to hand vitest the whole file
|
|
* list plus `--shard=i/n`, and vitest partitions by file COUNT. Runtime on this
|
|
* suite is wildly uneven — measured on the Windows runner, `cli-e2e` is 621 s
|
|
* and `worker-pool` 221 s, while most files are under a second — so a
|
|
* count-split routinely put several of the heaviest suites on one shard. That
|
|
* is #2449, and this file's sibling header has documented the symptom ("the
|
|
* heaviest spawn suites can cluster on one shard") since the watchdog was first
|
|
* raised from 15 to 20 minutes.
|
|
*
|
|
* It went from a latent hazard to a red matrix when three CHEAP files (the
|
|
* `dist/` module-load closure guards: 448 ms, 53 ms, sub-second) were added to
|
|
* `SPAWN_CLI`. They cost nothing to run, but a count-split re-partitions on
|
|
* every insertion, and the reshuffle happened to land `cli-e2e` + `cli-limit-e2e`
|
|
* + `analyze-heap-oom-e2e` together on shard 1/3 — 32 files against 26 and 29 —
|
|
* which blew the 20-minute budget with four files still queued. Nothing about
|
|
* the added files caused it; they were simply the perturbation.
|
|
*
|
|
* So the split is done HERE, by weight, and only the chosen shard's files are
|
|
* handed to vitest. Two properties follow, and both are pinned in
|
|
* `test/unit/cross-platform-shard.test.ts`:
|
|
*
|
|
* - the heaviest suites are spread across shards by construction, so the
|
|
* busiest shard tracks the ideal rather than the luck of the sort order;
|
|
* - adding or removing a CHEAP file cannot move a heavy one, so registering a
|
|
* new platform-sensitive test is no longer a CI-stability gamble. That is the
|
|
* property whose absence caused this.
|
|
*/
|
|
|
|
/**
|
|
* Measured wall-clock on the WINDOWS runner (the slowest platform, so it is the
|
|
* one that decides the budget), in seconds, from the last fully-green matrix run
|
|
* plus the timed files of the run that failed.
|
|
*
|
|
* Only files heavy enough to matter are listed; everything else is carried by
|
|
* {@link PER_FILE_OVERHEAD_SEC} alone. These are load-balancing hints, NOT
|
|
* assertions — no test measures elapsed time against this table. Missing or
|
|
* stale heavy entries can still overload a shard; refresh them from failed
|
|
* CI logs and replay that profile in the partition tests. Deliberately not
|
|
* auto-generated: a committed table is reviewable and works offline, and the
|
|
* alternative (timing files at CI runtime to decide the split) would make the
|
|
* partition depend on the very machine load it is trying to protect against.
|
|
*/
|
|
export const WINDOWS_WEIGHTS_SEC: Readonly<Record<string, number>> = {
|
|
// Re-measured after the analyze --watch e2e landed in #3072. The previous
|
|
// 361 s entry undercharged this suite and left shard 1 close to the watchdog.
|
|
'test/integration/cli-e2e.test.ts': 621,
|
|
'test/integration/worker-pool.test.ts': 222,
|
|
'test/unit/incremental-vector-extension-ordering.test.ts': 87,
|
|
// Measured on Windows in run 34014266125 (#3190, 2026-09-06). These DB
|
|
// suites landed together on shard 3: the old 180s estimate and missing
|
|
// entries made a ~27-minute recorded load look like an ~12-minute shard.
|
|
// Upstream speedups may reduce these figures; retaining conservative weights
|
|
// keeps the expensive suites distributed without changing the watchdog.
|
|
'test/unit/incremental-index-extension-dml-gate.test.ts': 414,
|
|
// Re-measured on windows-latest run 34815870795 after vendored-first FTS
|
|
// rewrote the HOME-layout e2e (373s) and skills-e2e grew to 542s. The old
|
|
// 146s/444s entries packed both onto shard 2/3 and blew the 20-minute
|
|
// watchdog with one file still queued.
|
|
'test/integration/skills-e2e.test.ts': 550,
|
|
'test/integration/fts-extension-e2e.test.ts': 380,
|
|
'test/integration/skip-fts.test.ts': 110,
|
|
'test/integration/analyze-wal-checkpoint-failure.test.ts': 86,
|
|
'test/integration/cli-limit-e2e.test.ts': 75,
|
|
'test/unit/hooks.test.ts': 26,
|
|
'test/integration/analyze-heap-oom-e2e.test.ts': 23,
|
|
'test/unit/git-utils.test.ts': 18,
|
|
'test/integration/hooks-e2e.test.ts': 15,
|
|
'test/integration/tree-sitter-languages.test.ts': 9,
|
|
'test/unit/repo-manager.test.ts': 9,
|
|
'test/unit/detect-changes-worktree.test.ts': 9,
|
|
'test/integration/antigravity-hook-e2e.test.ts': 7,
|
|
'test/unit/index-lock.test.ts': 5,
|
|
'test/unit/setup.test.ts': 5,
|
|
// ESTIMATE, not a measurement. This file asserts almost nothing; it READS —
|
|
// one 4893-file pass over every tracked text file, plus an 830-file pass over
|
|
// `src/`. Measured at 2.3 s and 0.3 s per pass on a virtualised and a local
|
|
// Linux filesystem respectively, so the cost is entirely per-file open
|
|
// latency, which is the term Windows inflates most (NTFS plus Defender on
|
|
// every read). Scaled from the slower Linux figure to keep the split
|
|
// conservative rather than let the 8 s PER_FILE_OVERHEAD floor under-charge
|
|
// a file that touches more paths than anything else here. Replace with a real
|
|
// figure after the first green Windows matrix run.
|
|
'test/unit/source-control-bytes.test.ts': 15,
|
|
};
|
|
|
|
/**
|
|
* Fixed cost every file pays regardless of what it asserts: a pool worker start,
|
|
* module graph evaluation, and (for most of this list) a native addon load.
|
|
*
|
|
* Added to EVERY file's weight, not just unmeasured ones, and that is the point.
|
|
* Calibrated against the last green Windows matrix: its busiest shard ran 736 s
|
|
* of wall clock over ~511 s of measured file time, so roughly 8 s per file is
|
|
* unattributed setup. Without this term the balancer treats a light file as
|
|
* nearly free and, having isolated the two monsters, piles every remaining file
|
|
* onto the other shards — trading a runtime imbalance for a file-count one that
|
|
* costs just as much. With it, the split balances runtime AND count together.
|
|
*/
|
|
const PER_FILE_OVERHEAD_SEC = 8;
|
|
|
|
/**
|
|
* Scheduling weight for `file`: its measured runtime (0 if it was fast enough
|
|
* that vitest printed no duration) plus the per-file floor above.
|
|
*/
|
|
export function weightOf(file: string): number {
|
|
return (WINDOWS_WEIGHTS_SEC[file] ?? 0) + PER_FILE_OVERHEAD_SEC;
|
|
}
|
|
|
|
/**
|
|
* Partition `files` into `total` shards and return the 1-based `index` one.
|
|
*
|
|
* Longest-processing-time first: sort by weight descending, then repeatedly give
|
|
* the next file to the lightest shard so far. LPT is the standard greedy for
|
|
* multiprocessor scheduling and is guaranteed within 4/3 of optimal — far more
|
|
* than enough here, where the goal is only "no shard gets two monsters".
|
|
*
|
|
* Ties break on the file path so the partition is DETERMINISTIC: every shard
|
|
* computes the same split independently, on a different machine, with no
|
|
* coordination — which is what lets each runner select its own slice.
|
|
*
|
|
* Returns files in the input list's original order, not weight order, so failure
|
|
* output and reruns stay readable.
|
|
*/
|
|
export function shardFiles(
|
|
files: readonly string[],
|
|
index: number,
|
|
total: number,
|
|
): readonly string[] {
|
|
if (!Number.isInteger(total) || total < 1) {
|
|
throw new Error(`shard total must be a positive integer, got ${total}`);
|
|
}
|
|
if (!Number.isInteger(index) || index < 1 || index > total) {
|
|
throw new Error(`shard index must be in 1..${total}, got ${index}`);
|
|
}
|
|
if (total === 1) return [...files];
|
|
|
|
const byWeightDesc = [...files].sort((a, b) => {
|
|
const diff = weightOf(b) - weightOf(a);
|
|
return diff !== 0 ? diff : a.localeCompare(b);
|
|
});
|
|
|
|
const loads = Array.from({ length: total }, () => 0);
|
|
const assigned = Array.from({ length: total }, () => new Set<string>());
|
|
for (const file of byWeightDesc) {
|
|
let lightest = 0;
|
|
for (let i = 1; i < total; i++) {
|
|
if (loads[i]! < loads[lightest]!) lightest = i;
|
|
}
|
|
assigned[lightest]!.add(file);
|
|
loads[lightest]! += weightOf(file);
|
|
}
|
|
|
|
const mine = assigned[index - 1]!;
|
|
return files.filter((f) => mine.has(f));
|
|
}
|
|
|
|
/** Total weight of a file set — the shard cost this balancer is minimising. */
|
|
export function shardWeight(files: readonly string[]): number {
|
|
return files.reduce((sum, f) => sum + weightOf(f), 0);
|
|
}
|