From aa364dbb6f65e49cae3753bf22d9e828abe49713 Mon Sep 17 00:00:00 2001 From: Kyle Tse Date: Fri, 25 Sep 2026 23:34:37 +0000 Subject: [PATCH 1/4] feat: lockdocs 0.3 - docs sites pinned to your major, heading-aware ranking, held-out benchmark questions - Docs-site repositories follow the pinned major: default branch for the latest, a vN / N.x branch or the last commit before the next major for older ones; pages about a later major are skipped. tokio's website added. - Docs pages written as React components (Tailwind installation guides) are indexed; MDX HTML headings and export const title are honoured. - Upstream reStructuredText in .txt files (Django) is now indexed. - Ranking: a second BM25 over headings and first sentences; question words that name a documented top-level API count as identifiers. - GitHub API redirects keep the token; docs-site errors are reported. - Benchmark: 17 held-out questions, grading-aware Context7 answer reuse, weight sweeps via workflow input. - docs/vision.md and docs/capabilities.md with a CI path check. --- .github/workflows/bench.yml | 9 +- .github/workflows/ci.yml | 3 +- AGENTS.md | 5 +- Cargo.lock | 4 +- Cargo.toml | 2 +- bench/questions.json | 278 +++++++++++++++++++- bench/render.py | 8 +- bench/run.py | 40 ++- crates/lockdocs-core/src/bm25.rs | 5 + crates/lockdocs-core/src/extract.rs | 295 ++++++++++++++++++++- crates/lockdocs-core/src/index.rs | 21 +- crates/lockdocs-core/src/markdown.rs | 83 +++++- crates/lockdocs-core/src/query.rs | 80 +++++- crates/lockdocs-core/src/upstream.rs | 299 +++++++++++++++++----- docs/.vitepress/config.ts | 2 + docs/capabilities.md | 21 ++ docs/guide/fetch.md | 10 +- docs/guide/how-it-works.md | 4 +- docs/public/bench-results.json | 210 ++++++++++----- docs/vision.md | 46 ++++ package.json | 2 +- packages/lockdocs/package.json | 12 +- packages/npm/darwin-arm64/package.json | 2 +- packages/npm/darwin-x64/package.json | 2 +- packages/npm/linux-arm64-gnu/package.json | 2 +- packages/npm/linux-x64-gnu/package.json | 2 +- packages/npm/win32-x64-msvc/package.json | 2 +- scripts/check-capabilities.ts | 24 ++ server.json | 4 +- 29 files changed, 1283 insertions(+), 194 deletions(-) create mode 100644 docs/capabilities.md create mode 100644 docs/vision.md create mode 100644 scripts/check-capabilities.ts diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 12dca64..204ca7a 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -11,6 +11,10 @@ on: description: Also query Context7 (anonymous tier) type: boolean default: true + variants: + description: 'Extra configurations, space-separated name:KEY=VALUE,KEY=VALUE (e.g. head0:LOCKDOCS_HEAD_WEIGHT=0)' + type: string + default: '' pull_request: paths: ['bench/**', '.github/workflows/bench.yml'] @@ -44,10 +48,13 @@ jobs: - name: Run (package files, then `lockdocs fetch` + upstream docs) env: C7: ${{ (github.event_name != 'workflow_dispatch' || inputs.context7) && '--context7' || '' }} + VARIANTS: ${{ inputs.variants }} GITHUB_TOKEN: ${{ github.token }} run: | set -euo pipefail - python3 bench/run.py target/release/lockdocs bench/projects bench.json --fetch $C7 --context7-cache docs/public/bench-results.json | tee -a "$GITHUB_STEP_SUMMARY" + extra=() + for v in $VARIANTS; do extra+=(--variant "$v"); done + python3 bench/run.py target/release/lockdocs bench/projects bench.json --fetch $C7 --context7-cache docs/public/bench-results.json "${extra[@]}" | tee -a "$GITHUB_STEP_SUMMARY" - uses: actions/upload-artifact@v7 with: name: bench diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5c2f691..dcbb116 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,12 +50,13 @@ jobs: components: clippy, rustfmt - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - run: bun install --frozen-lockfile - - name: Manifests agree on one version and one tagline + - name: Manifests agree on one version and one tagline; capability paths exist env: GH_TOKEN: ${{ github.token }} run: | bun scripts/check-version.ts bun scripts/check-tagline.ts "$(gh api "repos/$GITHUB_REPOSITORY" --jq .description)" + bun scripts/check-capabilities.ts - name: Format and lints run: cargo fmt --all --check && cargo clippy --workspace --locked -- -D warnings - name: Build release binary diff --git a/AGENTS.md b/AGENTS.md index c6edf62..da308a3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,7 +7,10 @@ Read [PROJECT.md](PROJECT.md) for the layout and the release flow. run it in CI (`bench.yml`), not on a shared machine. - Keep the MCP surface at three tools: `resolve`, `docs`, `api`. - Bump `index::FORMAT` whenever extraction output or `Entry` changes, so old - caches are rebuilt. + caches are rebuilt; bump `upstream::FORMAT` when `fetch` downloads more, so + `lockdocs fetch` refreshes old copies. +- Ranking changes are judged on the whole benchmark in CI, never on one + question; questions marked `held-out` are not used for tuning. - A new lockfile format: add a pure parser to `lockfile.rs` with a unit test, and register it in `LOCKFILES` or `parse`. - One version everywhere: `bun scripts/set-version.ts` (CI runs diff --git a/Cargo.lock b/Cargo.lock index f2a39a8..bc24ad8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -589,7 +589,7 @@ dependencies = [ [[package]] name = "lockdocs" -version = "0.2.1" +version = "0.3.0" dependencies = [ "anyhow", "lockdocs-core", @@ -599,7 +599,7 @@ dependencies = [ [[package]] name = "lockdocs-core" -version = "0.2.1" +version = "0.3.0" dependencies = [ "anyhow", "dirs 7.0.0", diff --git a/Cargo.toml b/Cargo.toml index b1be1cd..c8d7505 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = ["crates/lockdocs-core", "crates/lockdocs"] resolver = "2" [workspace.package] -version = "0.2.1" +version = "0.3.0" edition = "2021" license = "MIT" repository = "https://github.com/SylphxAI/lockdocs" diff --git a/bench/questions.json b/bench/questions.json index faf4132..1881c02 100644 --- a/bench/questions.json +++ b/bench/questions.json @@ -426,9 +426,9 @@ ] ], "reject": [ - "ConfigDict" + "model_config" ], - "why": "v1: class Config: extra = Extra.allow", + "why": "v1: class Config: extra = Extra.allow. model_config is v2-only (0 matches in pydantic 1.10.18). The reject was ConfigDict until 0.3, but pydantic 1.10 ships a ConfigDict TypedDict (config.py), so quoting v1 source was wrongly failed", "line": "older" }, { @@ -1081,6 +1081,280 @@ "reject": [], "why": "16: middleware renamed to proxy (next 16.3.6 dist/server/web/types.d.ts: '@deprecated Use NextProxy instead. Middleware has been renamed to Proxy.')", "line": "newer" + }, + { + "id": "next14-params", + "project": "next14", + "package": "next", + "question": "How do I read the dynamic route parameters in a page component?", + "expect": [ + [ + "params" + ] + ], + "reject": [ + "await params", + "Promise<{" + ], + "why": "14: params is a plain object prop (no 'await params' in v14.2.35 docs/02-app/02-api-reference/02-file-conventions/page.mdx)", + "line": "older", + "set": "held-out" + }, + { + "id": "next15-params", + "project": "next15", + "package": "next", + "question": "How do I read the dynamic route parameters in a page component?", + "expect": [ + [ + "await params", + "Promise<{" + ] + ], + "reject": [], + "why": "15: params is a Promise (v15.1.0 page.mdx, 18 matches)", + "line": "newer", + "set": "held-out" + }, + { + "id": "tw3-dark", + "project": "tailwind3", + "package": "tailwindcss", + "question": "How do I switch dark mode to a class instead of the operating system setting?", + "expect": [ + [ + "darkMode" + ] + ], + "reject": [ + "@custom-variant" + ], + "why": "v3: darkMode: 'selector' / 'class' in tailwind.config.js (v3 site dark-mode.mdx)", + "line": "older", + "set": "held-out" + }, + { + "id": "tw4-dark", + "project": "tailwind4", + "package": "tailwindcss", + "question": "How do I switch dark mode to a class instead of the operating system setting?", + "expect": [ + [ + "@custom-variant" + ] + ], + "reject": [], + "why": "v4: @custom-variant dark (&:where(.dark, .dark *)) in CSS (v4 site dark-mode.mdx)", + "line": "newer", + "set": "held-out" + }, + { + "id": "react18-ref", + "project": "react18", + "package": "react", + "question": "How do I pass a ref through to a child component?", + "expect": [ + [ + "forwardRef" + ] + ], + "reject": [], + "why": "18: forwardRef (@types/react 18.3.31)", + "line": "older", + "set": "held-out" + }, + { + "id": "react19-ref", + "project": "react19", + "package": "react", + "question": "How do I pass a ref through to a child component?", + "expect": [ + [ + "as a prop", + "no longer necessary" + ] + ], + "reject": [], + "why": "19: ref is a regular prop; forwardRef is no longer necessary (react.dev forwardRef.md)", + "line": "newer", + "set": "held-out" + }, + { + "id": "rr6-types", + "project": "rr6", + "package": "react-router", + "question": "How do I get typed loader data in my route component?", + "expect": [ + [ + "useLoaderData" + ] + ], + "reject": [ + "Route.ComponentProps", + "+types" + ], + "why": "v6: useLoaderData() (docs/hooks/use-loader-data.md); generated route types arrived in v7", + "line": "older", + "set": "held-out" + }, + { + "id": "rr7-types", + "project": "rr7", + "package": "react-router", + "question": "How do I get typed loader data in my route component?", + "expect": [ + [ + "Route.ComponentProps", + "+types" + ] + ], + "reject": [], + "why": "v7: generated ./+types/ and Route.ComponentProps (react-router@7.1.1 docs/start/framework/data-loading.md)", + "line": "newer", + "set": "held-out" + }, + { + "id": "eslint8-globals", + "project": "eslint8", + "package": "eslint", + "question": "How do I tell ESLint about browser global variables?", + "expect": [ + [ + "\"browser\": true", + "browser: true" + ] + ], + "reject": [], + "why": "8: env: { browser: true } in .eslintrc (v8.57.1 language-options.md)", + "line": "older", + "set": "held-out" + }, + { + "id": "eslint9-globals", + "project": "eslint9", + "package": "eslint", + "question": "How do I tell ESLint about browser global variables?", + "expect": [ + [ + "globals.browser" + ] + ], + "reject": [], + "why": "9: languageOptions: { globals: globals.browser } (v9.39.5 language-options.md)", + "line": "newer", + "set": "held-out" + }, + { + "id": "dj51-generated", + "project": "django51", + "package": "django", + "question": "How do I add a model field whose value the database computes from other columns?", + "expect": [ + [ + "GeneratedField" + ] + ], + "reject": [], + "why": "5.0+: models.GeneratedField (5.1.15 docs/ref/models/fields.txt)", + "line": "newer", + "set": "held-out" + }, + { + "id": "fa0115-querymodel", + "project": "fastapi0115", + "package": "fastapi", + "question": "How do I declare a group of query parameters with a Pydantic model?", + "expect": [ + [ + "Query Parameter Models", + "FilterParams", + "query parameter model" + ] + ], + "reject": [], + "why": "0.115.0+: query parameter models (docs/en/docs/tutorial/query-param-models.md)", + "line": "newer", + "set": "held-out" + }, + { + "id": "tokio-channel", + "project": "axum08", + "package": "tokio", + "question": "How do I send messages from many tasks to a single consumer task?", + "expect": [ + [ + "mpsc" + ] + ], + "reject": [], + "why": "tokio::sync::mpsc", + "line": "single", + "set": "held-out" + }, + { + "id": "zod3-datetime", + "project": "zod3", + "package": "zod", + "question": "How do I validate an ISO 8601 datetime string?", + "expect": [ + [ + ".datetime(" + ] + ], + "reject": [ + "z.iso." + ], + "why": "3.23: z.string().datetime(); z.iso arrived in zod 4 (0 matches in v3.23.8 README)", + "line": "older", + "set": "held-out" + }, + { + "id": "zod4-datetime", + "project": "zod4", + "package": "zod", + "question": "How do I validate an ISO 8601 datetime string?", + "expect": [ + [ + "z.iso.datetime", + "iso.datetime(" + ] + ], + "reject": [], + "why": "4: z.iso.datetime() (v4.1.5 packages/docs/content/api.mdx)", + "line": "newer", + "set": "held-out" + }, + { + "id": "pyd1-frozen", + "project": "pydantic1", + "package": "pydantic", + "question": "How do I make model instances immutable?", + "expect": [ + [ + "allow_mutation" + ] + ], + "reject": [ + "model_config" + ], + "why": "v1: class Config: allow_mutation = False / frozen = True (v1.10.18 docs/usage/model_config.md)", + "line": "older", + "set": "held-out" + }, + { + "id": "pyd2-frozen", + "project": "pydantic2", + "package": "pydantic", + "question": "How do I make model instances immutable?", + "expect": [ + [ + "frozen=True" + ] + ], + "reject": [], + "why": "v2: model_config = ConfigDict(frozen=True) (v2.9.2 docs/concepts/models.md)", + "line": "newer", + "set": "held-out" } ] } diff --git a/bench/render.py b/bench/render.py index f648ea0..c157d95 100755 --- a/bench/render.py +++ b/bench/render.py @@ -34,7 +34,7 @@ def sub(key, line): - rs = [r for r in rows if r.get("line") == line] + rs = [r for r in rows if (r.get("set") == "held-out" if line == "held-out" else r.get("line") == line)] got = sum(1 for r in rs if (r.get("context7", {}) if key == "context7" else r["variants"].get(key, {})).get("pass")) return f"{got}/{len(rs)}" @@ -48,13 +48,13 @@ def fmt_ms(v): "An answer passes when it contains the version-correct API and none of the other version's. Same questions and grader against Context7's anonymous API, " f"on a GitHub-hosted runner" + (f" ([run]({url}))" if url else "") + ":", "", - f"| | correct | older majors | newer majors | {', '.join(single) or 'single version'} | median tokens | median latency |", - "|---|---|---|---|---|---|---|", + f"| | correct | older majors | newer majors | {', '.join(single) or 'single version'} | held-out | median tokens | median latency |", + "|---|---|---|---|---|---|---|---|", ] for key, label in [("fetched", "lockdocs + `lockdocs fetch`"), ("hybrid", "lockdocs, package files only"), ("context7", "Context7 (anonymous)")]: a = res["summary"].get(key) if a: - table.append(f"| {label} | {a['passed']}/{a['total']} | {sub(key, 'older')} | {sub(key, 'newer')} | {sub(key, 'single')} | {a['median_tokens']:,} | {fmt_ms(a['median_ms'])} |") + table.append(f"| {label} | {a['passed']}/{a['total']} | {sub(key, 'older')} | {sub(key, 'newer')} | {sub(key, 'single')} | {sub(key, 'held-out')} | {a['median_tokens']:,} | {fmt_ms(a['median_ms'])} |") rp = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "README.md") readme = open(rp).read() readme = re.sub(r".*?", "\n" + "\n".join(table) + "\n", readme, flags=re.S) diff --git a/bench/run.py b/bench/run.py index bd545b5..04545b0 100755 --- a/bench/run.py +++ b/bench/run.py @@ -2,6 +2,12 @@ """lockdocs benchmark: version-specific questions against real installs. Usage: python3 bench/run.py [--context7] [--only id,id] + [--variant name:KEY=VALUE,KEY=VALUE ...] + +`--variant` adds a configuration run after `lockdocs fetch` with extra +environment (for weight sweeps on CI, e.g. `head25:LOCKDOCS_HEAD_WEIGHT=0.25`). +Questions marked `"set": "held-out"` were written after the last tuning round +and are reported separately, so tuning cannot quietly overfit the rest. Each question names a project (with dependencies installed at pinned versions) and a package. An answer passes when it contains at least one string from @@ -133,8 +139,18 @@ def run_lockdocs_env(binary, proj, q, env): os.environ[k] = v +def grader_key(q): + """Question identity for reusing a Context7 answer: text, package and grading.""" + return (q["id"], q["question"], q["package"], json.dumps([q["expect"], q.get("reject", [])])) + + def main(): binary, projects, out = sys.argv[1], sys.argv[2], sys.argv[3] + for i, a in enumerate(sys.argv): + if a == "--variant": + name, _, envs = sys.argv[i + 1].partition(":") + env = dict(kv.split("=", 1) for kv in envs.split(",") if kv) + VARIANTS.append((name, f"lockdocs, fetched, {envs}", env)) with_c7 = "--context7" in sys.argv do_fetch = "--fetch" in sys.argv # Reuse Context7 answers from a previous run for identical questions on the @@ -144,8 +160,9 @@ def main(): if "--context7-cache" in sys.argv: prev = json.load(open(sys.argv[sys.argv.index("--context7-cache") + 1])) for r in prev.get("rows", []): - if "pass" in r.get("context7", {}): - c7_cache[(r["id"], r["question"], r["package"], r["version"])] = r["context7"] + # Rows from before `grading` was recorded are keyed without it and never match. + if "pass" in r.get("context7", {}) and "grading" in r: + c7_cache[(r["id"], r["question"], r["package"], r["grading"], r["version"])] = r["context7"] reused = 0 only = None if "--only" in sys.argv: @@ -161,11 +178,11 @@ def main(): t = time.perf_counter() r = subprocess.run([binary, "index", "-C", proj, "--json"], capture_output=True, text=True, env={**os.environ, "LOCKDOCS_NO_UPSTREAM": "1"}) index[p] = {"ms": round((time.perf_counter() - t) * 1000), "report": json.loads(r.stdout) if r.returncode == 0 else r.stderr} - variants = [v for v in VARIANTS if do_fetch or v[0] != "fetched"] + variants = [v for v in VARIANTS if do_fetch or v[0] in ("keyword", "hybrid")] results = {} fetch = {} for name, _, env in variants: - if name == "fetched": + if name == "fetched" and not fetch: for p in projs: t = time.perf_counter() r = subprocess.run([binary, "fetch", "-C", os.path.join(projects, p), "--json"], capture_output=True, text=True) @@ -177,14 +194,14 @@ def main(): first = text.splitlines()[0] if text else "" version = first.split(" · ")[0].rsplit("@", 1)[-1] if "@" in first else "" results[(name, q["id"])] = ({"pass": ok and code == 0, "missing": missing, "rejected": rej, "tokens": count(text), "ms": round(ms, 1)}, version) - main_variant = variants[-1][0] + main_variant = "fetched" if do_fetch else variants[-1][0] rows = [] for q in qs: main_res, version = results[(main_variant, q["id"])] row = {"id": q["id"], "project": q["project"], "package": q["package"], "version": version, "question": q["question"], "why": q["why"], "line": q.get("line", ""), - "lockdocs": main_res, "variants": {n: results[(n, q["id"])][0] for n, _, _ in variants}} + "set": q.get("set", "tuning"), "grading": grader_key(q)[3], "lockdocs": main_res, "variants": {n: results[(n, q["id"])][0] for n, _, _ in variants}} if with_c7: - hit = c7_cache.get((q["id"], q["question"], q["package"], version)) + hit = c7_cache.get(grader_key(q) + (version,)) if hit is not None: row["context7"] = dict(hit, reused=True) reused += 1 @@ -221,8 +238,9 @@ def markdown(res, with_c7): if with_c7: cols.append(("context7", "Context7 (anonymous API)")) out = [f"Tokenizer: {res['tokenizer']}. Runner: {res['runner']['os']} {res['runner']['machine']}. {len(res['rows'])} questions.", ""] - groups = [("older", "older major"), ("newer", "newer major"), ("single", "single version")] - present = [g for g in groups if any(r.get("line") == g[0] for r in res["rows"])] + groups = [("older", "older major"), ("newer", "newer major"), ("single", "single version"), ("held-out", "held-out")] + in_group = lambda r, g: r.get("set") == "held-out" if g == "held-out" else r.get("line") == g + present = [g for g in groups if any(in_group(r, g[0]) for r in res["rows"])] out.append("| | correct | " + " | ".join(t for _, t in present) + " | median tokens | median latency | p95 latency |") out.append("|---|---|" + "---|" * len(present) + "---|---|---|") for key, label in cols: @@ -231,7 +249,7 @@ def markdown(res, with_c7): continue subs = [] for g, _ in present: - rs = [r for r in res["rows"] if r.get("line") == g] + rs = [r for r in res["rows"] if in_group(r, g)] subs.append(f"{sum(1 for r in rs if result_of(r, key).get('pass'))}/{len(rs)}") out.append(f"| {label} | {a['passed']}/{a['total']} | " + " | ".join(subs) + f" | {a['median_tokens']} | {a['median_ms']:.0f} ms | {a['p95_ms']:.0f} ms |") if with_c7 and res["context7_calls"]: @@ -245,7 +263,7 @@ def markdown(res, with_c7): for key, _ in cols: x = result_of(r, key) marks.append("✅" if x.get("pass") else ("❌" if "pass" in x else f"— ({x.get('error')})")) - line = f"| {r['id']} | {r['package']}@{r['version']} | " + " | ".join(marks) + f" | {r['lockdocs']['tokens']} | {r['lockdocs']['ms']:.0f} |" + line = f"| {r['id']}{' (held-out)' if r.get('set') == 'held-out' else ''} | {r['package']}@{r['version']} | " + " | ".join(marks) + f" | {r['lockdocs']['tokens']} | {r['lockdocs']['ms']:.0f} |" if with_c7: c = r.get("context7", {}) line += f" {c.get('library', '')} ({c.get('version_match', '')}) |" diff --git a/crates/lockdocs-core/src/bm25.rs b/crates/lockdocs-core/src/bm25.rs index d0b2579..6b8e087 100644 --- a/crates/lockdocs-core/src/bm25.rs +++ b/crates/lockdocs-core/src/bm25.rs @@ -12,6 +12,11 @@ const STOP: &[&str] = &[ "their", "into", "vs", "versus", "about", "example", "show", ]; +/// A stopword (lowercase)? +pub fn is_stop(w: &str) -> bool { + STOP.contains(&w) +} + /// Conservative suffix stripping: plural, -ing, -ed, final -e. pub fn stem(t: &str) -> String { let mut s = stem0(t); diff --git a/crates/lockdocs-core/src/extract.rs b/crates/lockdocs-core/src/extract.rs index 2a556ce..aec0200 100644 --- a/crates/lockdocs-core/src/extract.rs +++ b/crates/lockdocs-core/src/extract.rs @@ -215,6 +215,8 @@ struct Job { #[derive(Clone, Copy, PartialEq)] enum How { Prose, + /// A docs page written as a JS/TSX component. + Page, Example, Metadata, Ts, @@ -293,12 +295,27 @@ fn plan(eco: Eco, src: &Source, extra_types: Option<&Path>, upstream: Option<&Pa } // Upstream docs fetched from the repository at this version's tag. if let Some(up) = upstream { + let pages: HashSet = std::fs::read_to_string(up.join(".lockdocs-upstream.json")) + .ok() + .and_then(|t| serde_json::from_str::(&t).ok()) + .map(|m| m.pages.into_iter().collect()) + .unwrap_or_default(); let mut rels = Vec::new(); walk_all(up, Path::new(""), 0, &mut rels); for r in rels { let e = ext_of(&r); - let rel = format!("upstream:{}", r.to_string_lossy().replace('\\', "/")); - if doc_ext(&e) || e == "mdoc" { + let plain = r.to_string_lossy().replace('\\', "/"); + let rel = format!("upstream:{plain}"); + if pages.contains(&plain) { + jobs.push(Job { + abs: up.join(&r), + rel, + how: How::Page, + }); + continue; + } + // Django and others write reStructuredText in `.txt` files under docs/. + if doc_ext(&e) || e == "mdoc" || e == "txt" { jobs.push(Job { abs: up.join(&r), rel, @@ -378,6 +395,12 @@ pub fn extract(eco: Eco, pkg_name: &str, src: &Source, extra_types: Option<&Path } } } + How::Page => { + if let Some((md, title)) = page_markdown(&text) { + let title = title.unwrap_or_else(|| page_title(&job.rel)); + prose_titled(&md, &job.rel, 0, Some(title), &mut out); + } + } How::Example => example(&text, &job.rel, &mut out), How::Metadata => { let (body, line) = markdown::metadata_body(&text); @@ -505,10 +528,238 @@ fn example(text: &str, rel: &str, out: &mut Vec) { }); } +/// Title for a page file without one: its route (`docs/installation/using-vite`). +fn page_title(rel: &str) -> String { + let segs: Vec<&str> = rel.split('/').filter(|s| !(s.starts_with('(') && s.ends_with(')'))).collect(); + let mut segs: Vec<&str> = segs.iter().rev().take(3).rev().copied().collect(); + if let Some(last) = segs.last() { + if last.starts_with("page.") || last.starts_with("index.") { + segs.pop(); + } + } + segs.join("/") + .trim_end_matches(".tsx") + .trim_end_matches(".jsx") + .trim_end_matches(".js") + .to_string() +} + +/// Markdown from a docs page written as a JS/TSX component: `title:` values +/// of listed steps become headings, `description:` and JSX text become +/// paragraphs, `code:` strings become fenced code (language from `lang:`), +/// and JSX `

`..`

` stay headings. Lines keep their source position +/// where they can. Returns (markdown, page title from `metadata.title`). +fn page_markdown(text: &str) -> Option<(String, Option)> { + if text.len() > 200_000 { + return None; + } + parse_with("tsx", text, |root, src| { + let mut p = PageOut::default(); + page_walk(root, src, &mut p, false); + p.flush(); + let mut md = String::new(); + let mut n = 0u32; + for (line, t) in &p.out { + while n + 1 < *line { + md.push('\n'); + n += 1; + } + md.push_str(t); + md.push('\n'); + n += 1 + t.matches('\n').count() as u32; + } + (md, p.title) + }) + .filter(|(md, _)| !md.trim().is_empty()) +} + +#[derive(Default)] +struct PageOut { + out: Vec<(u32, String)>, + para: String, + para_line: u32, + title: Option, +} + +impl PageOut { + fn text(&mut self, line: u32, t: &str) { + if self.para.trim().is_empty() { + self.para_line = line; + } + self.para.push_str(t); + } + fn flush(&mut self) { + let t = compact(&self.para, usize::MAX); + if !t.is_empty() { + self.out.push((self.para_line, t)); + } + self.para.clear(); + } + fn block(&mut self, line: u32, t: String) { + self.flush(); + self.out.push((line, t)); + } +} + +fn line_of(n: Node) -> u32 { + n.start_position().row as u32 + 1 +} + +/// The value of a string, template string or tagged template (`dedent`...``). +fn js_string(n: Node, src: &[u8]) -> Option { + match n.kind() { + "string" => { + let raw = txt(n, src); + let inner = raw.get(1..raw.len().saturating_sub(1)).unwrap_or(""); + Some(inner.replace("\\n", "\n").replace("\\'", "'").replace("\\\"", "\"").replace("\\\\", "\\")) + } + "template_string" => { + let raw = txt(n, src); + let inner = raw.get(1..raw.len().saturating_sub(1)).unwrap_or(""); + Some(dedent(&inner.trim_matches('\n').lines().collect::>())) + } + "call_expression" => n + .child_by_field_name("arguments") + .filter(|a| a.kind() == "template_string") + .and_then(|a| js_string(a, src)), + "parenthesized_expression" => n.named_child(0).and_then(|c| js_string(c, src)), + _ => None, + } +} + +fn jsx_name(n: Node, src: &[u8]) -> String { + let open = if n.kind() == "jsx_element" { + n.child_by_field_name("open_tag") + } else { + Some(n) + }; + open.and_then(|o| o.child_by_field_name("name")) + .map(|x| txt(x, src).to_string()) + .unwrap_or_default() +} + +/// The visible text inside a JSX element. +fn jsx_text(n: Node, src: &[u8], out: &mut String) { + match n.kind() { + "jsx_text" => out.push_str(txt(n, src)), + "jsx_expression" => { + if let Some(v) = n.named_child(0).and_then(|c| js_string(c, src)) { + out.push_str(&v); + } + } + "jsx_attribute" | "jsx_opening_element" | "jsx_closing_element" => {} + _ => { + let mut c = n.walk(); + for ch in n.named_children(&mut c) { + jsx_text(ch, src, out); + } + } + } +} + +fn page_walk(n: Node, src: &[u8], p: &mut PageOut, in_array: bool) { + match n.kind() { + "import_statement" | "comment" | "jsx_attribute" => {} + "pair" => { + let key = n + .child_by_field_name("key") + .map(|k| txt(k, src).trim_matches(['"', '\'']).to_string()) + .unwrap_or_default(); + let Some(value) = n.child_by_field_name("value") else { return }; + match key.as_str() { + "title" | "heading" => { + if let Some(t) = js_string(value, src) { + if !in_array && p.title.is_none() { + p.title = Some(t); + } else if in_array { + p.block(line_of(n), format!("## {}", compact(&t, 200))); + } + } + } + "description" => { + if let Some(t) = js_string(value, src) { + p.block(line_of(value), compact(&t, usize::MAX)); + } + } + "code" => match js_string(value, src) { + Some(code) => { + let lang = n + .parent() + .and_then(|obj| { + let mut c = obj.walk(); + let found = obj.named_children(&mut c).find(|x| { + x.kind() == "pair" && x.child_by_field_name("key").is_some_and(|k| txt(k, src).trim_matches(['"', '\'']) == "lang") + }); + found + }) + .and_then(|x| x.child_by_field_name("value")) + .and_then(|v| js_string(v, src)) + .unwrap_or_default(); + p.block(line_of(value), format!("```{lang}\n{}\n```", code.trim_end())); + } + None => page_walk(value, src, p, in_array), + }, + "name" | "lang" | "className" | "href" | "id" | "url" | "src" | "image" | "images" | "icon" | "openGraph" | "twitter" | "alternates" => {} + _ => page_walk(value, src, p, in_array), + } + } + "jsx_element" | "jsx_self_closing_element" => { + let name = jsx_name(n, src); + let b = name.as_bytes(); + if b.len() == 2 && b[0] == b'h' && (b'1'..=b'6').contains(&b[1]) { + let mut t = String::new(); + jsx_text(n, src, &mut t); + let t = compact(&t, 200); + if !t.is_empty() { + p.block(line_of(n), format!("{} {t}", "#".repeat((b[1] - b'0') as usize))); + } + } else if name == "code" { + let mut t = String::new(); + jsx_text(n, src, &mut t); + p.text(line_of(n), &format!("`{}`", t.trim())); + } else { + let block = matches!(name.as_str(), "p" | "li" | "div" | "section" | "ul" | "ol" | "pre" | "table" | "tr"); + if block { + p.flush(); + } + let mut c = n.walk(); + for ch in n.named_children(&mut c) { + page_walk(ch, src, p, in_array); + } + if block { + p.flush(); + } + } + } + "jsx_text" => p.text(line_of(n), txt(n, src)), + "jsx_expression" => match n.named_child(0) { + Some(c) if matches!(c.kind(), "string" | "template_string") => { + let v = js_string(c, src).unwrap_or_default(); + p.text(line_of(n), &v); + } + Some(c) => page_walk(c, src, p, in_array), + None => {} + }, + _ => { + let arr = in_array || n.kind() == "array"; + let mut c = n.walk(); + for ch in n.named_children(&mut c) { + page_walk(ch, src, p, arr); + } + } + } +} + fn prose(text: &str, rel: &str, line_off: u32, out: &mut Vec) { + prose_titled(text, rel, line_off, None, out) +} + +fn prose_titled(text: &str, rel: &str, line_off: u32, given: Option, out: &mut Vec) { let (text, fm_title) = markdown::clean_mdx(text, rel.ends_with(".mdx")); let file_title = rel.rsplit('/').next().unwrap_or(rel); - let title = if rel.contains("package metadata") { + let title = if let Some(t) = given { + t + } else if rel.contains("package metadata") { "README".to_string() } else if let Some(t) = fm_title { t @@ -1664,6 +1915,44 @@ export * as zz from "./external"; assert_eq!(get("pydantic.main.create_model").kind, Kind::Function); } + #[test] + fn docs_pages_written_as_components() { + let src = r#"import dedent from "dedent"; +export const metadata = { title: "Installing with Vite", description: "Use the Vite plugin.", openGraph: { title: "x" } }; +const steps = [ + { + title: "Import Tailwind CSS", + body: ( +

+ Add an @import to your CSS file. +

+ ), + code: { name: "CSS", lang: "css", code: dedent` + @import "tailwindcss"; + ` }, + }, + { title: 'Old way', body: () =>

Use directives{' '}here.

, code: { lang: 'css', code: '@tailwind base;\n@tailwind utilities;' } }, +]; +export default function Page() { return

Overview

Fast.

} +"#; + let (md, title) = page_markdown(src).unwrap(); + assert_eq!(title.as_deref(), Some("Installing with Vite")); + assert!(md.contains("## Import Tailwind CSS"), "{md}"); + assert!(md.contains("Add an `@import` to your CSS file."), "{md}"); + assert!(md.contains("```css\n@import \"tailwindcss\";\n```"), "{md}"); + assert!(md.contains("@tailwind base;\n@tailwind utilities;"), "{md}"); + assert!(md.contains("### Overview"), "{md}"); + assert!(!md.contains("dedent") && !md.contains("prose"), "{md}"); + let mut out = Vec::new(); + prose_titled(&md, "upstream:site/docs/installation/page.tsx", 0, title, &mut out); + let s = out.iter().find(|e| e.name == "Installing with Vite › Import Tailwind CSS").unwrap(); + assert_eq!(s.line, 5); + assert_eq!( + page_title("upstream:site/src/app/(docs)/docs/installation/(tabs)/using-vite/page.tsx"), + "installation/using-vite" + ); + } + #[test] fn rust_items_and_macro_bodies() { let src = "//! Crate docs.\n//!\n//! # Usage\n//! Call spawn.\n\ncfg_rt! {\n /// Spawns a new task.\n #[track_caller]\n pub fn spawn(future: F) -> JoinHandle\n where F: Future + Send + 'static {\n todo!()\n }\n}\n\n/// A router.\npub struct Router { inner: Inner }\n\nimpl Router {\n /// Add a route.\n pub fn route(self, path: &str, m: MethodRouter) -> Self { self }\n fn private(&self) {}\n}\n\n/// Hidden\n#[doc(hidden)]\npub fn hidden() {}\n\n/// Macro.\n#[macro_export]\nmacro_rules! select { () => {} }\n"; diff --git a/crates/lockdocs-core/src/index.rs b/crates/lockdocs-core/src/index.rs index 3f90525..3b51bd3 100644 --- a/crates/lockdocs-core/src/index.rs +++ b/crates/lockdocs-core/src/index.rs @@ -12,7 +12,7 @@ use std::path::{Path, PathBuf}; use std::time::Instant; /// Bump when extraction or the on-disk format changes. -pub const FORMAT: u32 = 8; +pub const FORMAT: u32 = 9; #[derive(Serialize, Deserialize)] pub struct PackageIndex { @@ -26,6 +26,10 @@ pub struct PackageIndex { pub entries: Vec, pub terms: Vec>, pub lens: Vec, + /// Terms of the heading or name plus the first sentence only, ranked on + /// their own so a long body does not bury what the entry says it is. + pub head: Vec>, + pub head_lens: Vec, pub build_ms: u64, /// `github.com/o/r@tag (N files)` when upstream docs are included. pub upstream: Option, @@ -93,6 +97,18 @@ pub fn entry_tf(e: &Entry) -> (Vec<(String, u16)>, u32) { } } +/// Heading (or name) and first sentence: what an entry says it is about. +pub fn head_tf(e: &Entry) -> (Vec<(String, u16)>, u32) { + let (prose, _) = split_code(&e.doc); + let first = summary(&prose); + if e.kind == Kind::Prose { + let last = e.name.rsplit(" › ").next().unwrap_or(""); + bm25::tf(&[(last, 2), (first, 1)]) + } else { + bm25::tf(&[(&e.name, 2), (first, 1)]) + } +} + /// Files whose change means the package was modified in place. fn stamp(src: &Source) -> String { let probe: Vec = match &src.metadata { @@ -168,6 +184,7 @@ pub fn build(dep: &Dep, src: &Source, root: &Path, up: Option<&(PathBuf, Manifes let up_dir = up.filter(|(_, m)| m.files > 0).map(|(d, _)| d.as_path()); let entries = extract::extract(dep.eco, &dep.name, src, types.as_deref(), up_dir); let (terms, lens): (Vec<_>, Vec<_>) = entries.iter().map(entry_tf).unzip(); + let (head, head_lens): (Vec<_>, Vec<_>) = entries.iter().map(head_tf).unzip(); let model = embed::get(); let vecs: Vec = match &model { Some(m) => { @@ -186,6 +203,8 @@ pub fn build(dep: &Dep, src: &Source, root: &Path, up: Option<&(PathBuf, Manifes entries, terms, lens, + head, + head_lens, build_ms: t.elapsed().as_millis() as u64, upstream: up .filter(|(_, m)| m.files > 0) diff --git a/crates/lockdocs-core/src/markdown.rs b/crates/lockdocs-core/src/markdown.rs index 06c94b7..6c9f297 100644 --- a/crates/lockdocs-core/src/markdown.rs +++ b/crates/lockdocs-core/src/markdown.rs @@ -199,7 +199,10 @@ fn push_sized(out: &mut Vec
, head: &str, line: u32, body: &str) { } /// Blank out front matter (keeping its `title`), and for MDX the `import` / -/// `export` lines and JSX-only lines. Line numbers are preserved. +/// `export` lines and JSX-only lines. `export const title = "..."` names the +/// page and `export const description` keeps its text. HTML headings +/// (`

@import

`, one or more lines) become +/// Markdown headings so sections split at them. Line numbers are preserved. pub fn clean_mdx(text: &str, mdx: bool) -> (String, Option) { let lines: Vec<&str> = text.lines().collect(); let mut out: Vec = Vec::with_capacity(lines.len()); @@ -225,21 +228,76 @@ pub fn clean_mdx(text: &str, mdx: bool) -> (String, Option) { } } let mut fence = false; - for l in &lines[i.min(lines.len())..] { + let mut j = i.min(lines.len()); + while j < lines.len() { + let l = lines[j]; let t = l.trim(); if t.starts_with("```") || t.starts_with("~~~") { fence = !fence; } + if !fence { + if let Some((level, text, used)) = html_heading(&lines[j..]) { + out.push(format!("{} {text}", "#".repeat(level))); + out.extend(std::iter::repeat_n(String::new(), used - 1)); + j += used; + continue; + } + } + if mdx && !fence { + if let Some(v) = export_const(t, "title") { + title = title.or(Some(v)); + out.push(String::new()); + j += 1; + continue; + } + if let Some(v) = export_const(t, "description") { + out.push(v); + j += 1; + continue; + } + } let drop = mdx && !fence && (t.starts_with("import ") || t.starts_with("export ") || (t.starts_with('<') && t.ends_with('>') && !t.starts_with(" -70 questions whose correct answer depends on the version, over 15 libraries (zod, Next.js, React Router, pydantic, axum, tokio, Tailwind CSS, ESLint, Prisma, React, Vite, Express, SQLAlchemy, Django, FastAPI), each asked in a real project with that version installed. An answer passes when it contains the version-correct API and none of the other version's. Same questions and grader against Context7's anonymous API, on a GitHub-hosted runner ([run](https://github.com/SylphxAI/lockdocs/actions/runs/36125903106)): +105 questions whose correct answer depends on the version, over 15 libraries (zod, Next.js, React Router, pydantic, axum, tokio, Tailwind CSS, ESLint, Prisma, React, Vite, Express, SQLAlchemy, Django, FastAPI), each asked in a real project with that version installed. An answer passes when it contains the version-correct API and none of the other version's. Same questions and grader against Context7's anonymous API, on a GitHub-hosted runner ([run](https://github.com/SylphxAI/lockdocs/actions/runs/36204404336)): -| | correct | older majors | newer majors | tokio | median tokens | median latency | -|---|---|---|---|---|---|---| -| lockdocs + `lockdocs fetch` | 55/70 | 24/33 | 29/34 | 2/3 | 875 | 87 ms | -| lockdocs, package files only | 46/70 | 22/33 | 22/34 | 2/3 | 915 | 49 ms | -| Context7 (anonymous) | 49/70 | 12/33 | 34/34 | 3/3 | 908 | 2,011 ms | +| | correct | older majors | newer majors | tokio | held-out | median tokens | median latency | +|---|---|---|---|---|---|---|---| +| lockdocs + `lockdocs fetch` | 96/105 | 38/45 | 53/55 | 5/5 | 16/18 | 866 | 97 ms | +| lockdocs, package files only | 60/105 | 27/45 | 29/55 | 4/5 | 6/18 | 903 | 52 ms | +| Context7 (anonymous) | 77/105 | 19/45 | 53/55 | 5/5 | 15/18 | 908 | 2,583 ms | -- **Where versions matter most, lockdocs wins by 2x.** On older majors Context7 often answers with the newest API (all five pydantic 1 questions got pydantic 2 answers). -- **Context7 is ahead on the newest majors (34/34 vs 29/34) and on tokio (3/3 vs 2/3)**, and we are working to close that. Its index covers docs websites that no package or tag ships (Prisma's docs now describe a later major), and lockdocs has a few ranking misses. The benchmark page lists every question and answer. -- **~23x faster, no quota.** lockdocs latency is a fresh CLI process per question; `lockdocs fetch` is a one-time 0.6-5 s per project (median 2.4 s). +- **Where versions matter most, lockdocs wins by 2x** (38/45 vs 19/45 on older majors). Context7 often answers with the newest API (pydantic 1 questions get pydantic 2 answers). +- **Tied on the newest majors (53/55 each) and on tokio (5/5 each); ahead on held-out questions (16/18 vs 15/18)**, which were written before the ranking changes they measure and not used for tuning. +- **~27x faster, fewer tokens, no quota.** lockdocs latency is a fresh CLI process per question; `lockdocs fetch` is a one-time download per project. Method, questions, per-question results and scripts: [benchmark page](https://sylphxai.github.io/lockdocs/benchmarks) and [`bench/`](bench/). diff --git a/bench/run.py b/bench/run.py index 2aa42e2..1a9cfad 100755 --- a/bench/run.py +++ b/bench/run.py @@ -270,7 +270,7 @@ def markdown(res, with_c7): if with_c7 and res["context7_calls"]: c = res["context7_calls"] out += ["", f"Context7 (anonymous): {c['calls']} HTTP calls, {c['rate_limited']} rate-limited (429), {c['errors']} other errors; ratelimit-limit header {c['limit']}, remaining {c['remaining']}." - + (f" {c['reused']} answers reused from the previous run's identical question and version (see bench/run.py --context7-cache)." if c.get("reused") else "")] + + (f" {c['reused']} answers reused from the previous run's identical question, grading and version (see bench/run.py --context7-cache)." if c.get("reused") else "")] head = "| question | version | " + " | ".join(n for n, _ in cols) + " | tokens (last lockdocs) | ms |" + (" Context7 library |" if with_c7 else "") out += ["", head, "|---|---|" + "---|" * len(cols) + "---|---|" + ("---|" if with_c7 else "")] for r in res["rows"]: diff --git a/docs/benchmarks.md b/docs/benchmarks.md index db313cf..3e8bebf 100644 --- a/docs/benchmarks.md +++ b/docs/benchmarks.md @@ -2,11 +2,12 @@ ## Method -- **Questions:** 70 questions whose correct answer depends on the version, in [`bench/questions.json`](https://github.com/SylphxAI/lockdocs/blob/main/bench/questions.json), over zod 3/4, Next.js 14/15/16, React Router 6/7, pydantic 1/2, axum 0.7/0.8, tokio, Tailwind CSS 3/4, ESLint 8/9, Prisma 5/6, React 18/19, Vite 5/6, Express 4/5, SQLAlchemy 1.4/2.0, Django 4.2/5.1 and FastAPI 0.88/0.115. Twin questions are worded identically for both versions; each records its source of truth. +- **Questions:** 105 questions whose correct answer depends on the version, in [`bench/questions.json`](https://github.com/SylphxAI/lockdocs/blob/main/bench/questions.json), over zod 3/4, Next.js 14/15/16, React Router 6/7, pydantic 1/2, axum 0.7/0.8, tokio, Tailwind CSS 3/4, ESLint 8/9, Prisma 5/6, React 18/19, Vite 5/6, Express 4/5, SQLAlchemy 1.4/2.0, Django 4.2/5.1 and FastAPI 0.88/0.115. Twin questions are worded identically for both versions; each records its source of truth. +- **Held-out questions:** 18 questions marked `held-out` are not used for tuning: they were written before the ranking changes they measure and have their own column. Held-out questions that are later used to diagnose a miss join the main set (their `history` field says so), and new ones replace them. The 0.3 round did this once: 17 questions first written as held-out (lockdocs 11/17, Context7 12/17 on their first run) are now in the main set. - **Projects:** one project per version in [`bench/projects`](https://github.com/SylphxAI/lockdocs/tree/main/bench/projects), installed at those pins by [`bench/setup.sh`](https://github.com/SylphxAI/lockdocs/blob/main/bench/setup.sh). - **Grading:** an answer passes when it contains at least one string from every `expect` group (the version-correct API, in code or prose form) and none of the `reject` strings (the other version's API). Case-insensitive; the same grader for every tool. - **lockdocs**, default settings (1,200-token budget), in three configurations: keyword only (`LOCKDOCS_EMBED=0`) on package files; hybrid on package files (the offline default once the model is downloaded); hybrid after `lockdocs fetch` added upstream docs at each version's tag. Index and fetch times are reported separately. -- **Context7:** the anonymous API as its MCP server uses it: search the library, pick the top result and its listed version with the same major (exact when listed), then fetch context for the question. Rate-limit responses are recorded, not retried. +- **Context7:** the anonymous API as its MCP server uses it: search the library, pick the top result and its listed version with the same major (exact when listed), then fetch context for the question. When that library answers HTTP 404, the next search result is tried, as an agent would. Rate-limit responses are recorded, not retried. - **Tokens:** tiktoken `o200k_base`. **Latency:** wall time per call from the same GitHub-hosted runner (for lockdocs: a fresh CLI process per question, including loading the model). - **Runner:** [`bench/run.py`](https://github.com/SylphxAI/lockdocs/blob/main/bench/run.py) via the [`bench` workflow](https://github.com/SylphxAI/lockdocs/actions/workflows/bench.yml). Reproduce: `bash bench/setup.sh && python3 bench/run.py target/release/lockdocs bench/projects out.json --fetch --context7`. @@ -14,133 +15,168 @@ -From [this run](https://github.com/SylphxAI/lockdocs/actions/runs/36125903106). +From [this run](https://github.com/SylphxAI/lockdocs/actions/runs/36204404336). -Tokenizer: tiktoken o200k_base. Runner: Linux x86_64. 70 questions. +Tokenizer: tiktoken o200k_base. Runner: Linux x86_64. 105 questions. -| | correct | older major | newer major | single version | median tokens | median latency | p95 latency | -|---|---|---|---|---|---|---|---| -| lockdocs, keyword only (BM25), package files | 41/70 | 20/33 | 19/34 | 2/3 | 898 | 22 ms | 397 ms | -| lockdocs, hybrid (BM25 + embeddings), package files | 46/70 | 22/33 | 22/34 | 2/3 | 915 | 49 ms | 94 ms | -| lockdocs, hybrid + upstream docs (after `lockdocs fetch`) | 55/70 | 24/33 | 29/34 | 2/3 | 875 | 87 ms | 491 ms | -| Context7 (anonymous API) | 49/70 | 12/33 | 34/34 | 3/3 | 908 | 2011 ms | 3327 ms | +| | correct | older major | newer major | single version | held-out | median tokens | median latency | p95 latency | +|---|---|---|---|---|---|---|---|---| +| lockdocs, keyword only (BM25), package files | 59/105 | 29/45 | 26/55 | 4/5 | 8/18 | 882 | 25 ms | 276 ms | +| lockdocs, hybrid (BM25 + embeddings), package files | 60/105 | 27/45 | 29/55 | 4/5 | 6/18 | 903 | 52 ms | 103 ms | +| lockdocs, hybrid + upstream docs (after `lockdocs fetch`) | 96/105 | 38/45 | 53/55 | 5/5 | 16/18 | 866 | 97 ms | 474 ms | +| Context7 (anonymous API) | 77/105 | 19/45 | 53/55 | 5/5 | 15/18 | 908 | 2583 ms | 3398 ms | -Context7 (anonymous): 4 HTTP calls, 0 rate-limited (429), 0 other errors; ratelimit-limit header 200, remaining 196. 67 answers reused from the previous run's identical question and version (see bench/run.py --context7-cache). +Context7 (anonymous): 8 HTTP calls, 0 rate-limited (429), 4 other errors; ratelimit-limit header 200, remaining 190. 103 answers reused from the previous run's identical question, grading and version (see bench/run.py --context7-cache). | question | version | keyword | hybrid | fetched | context7 | tokens (last lockdocs) | ms | Context7 library | |---|---|---|---|---|---|---|---|---| -| zod3-strict | zod@3.23.8 | ✅ | ✅ | ✅ | ✅ | 1019 | 84 | /colinhacks/zod/v3.24.2 (same major) | -| zod4-strict | zod@4.1.5 | ✅ | ❌ | ✅ | ✅ | 946 | 134 | /colinhacks/zod/v4.0.1 (same major) | -| zod3-email | zod@3.23.8 | ✅ | ✅ | ✅ | ✅ | 1167 | 40 | /colinhacks/zod/v3.24.2 (same major) | -| zod4-email | zod@4.1.5 | ✅ | ✅ | ✅ | ✅ | 965 | 59 | /colinhacks/zod/v4.0.1 (same major) | -| zod3-error | zod@3.23.8 | ✅ | ✅ | ✅ | ✅ | 985 | 46 | /colinhacks/zod/v3.24.2 (same major) | -| zod4-error | zod@4.1.5 | ✅ | ✅ | ✅ | ✅ | 989 | 52 | /colinhacks/zod/v4.0.1 (same major) | -| zod4-record | zod@4.1.5 | ❌ | ❌ | ✅ | ✅ | 1026 | 57 | /colinhacks/zod/v4.0.1 (same major) | -| next14-cookies | next@14.2.35 | ❌ | ✅ | ❌ | ❌ | 849 | 342 | /vercel/next.js/v14.3.0-canary.87 (same major) | -| next15-cookies | next@15.1.0 | ✅ | ✅ | ✅ | ✅ | 892 | 392 | /vercel/next.js/v15.1.11 (same major) | -| next14-headers | next@14.2.35 | ✅ | ✅ | ✅ | ✅ | 892 | 80 | /vercel/next.js/v14.3.0-canary.87 (same major) | -| next15-headers | next@15.1.0 | ✅ | ✅ | ✅ | ✅ | 872 | 84 | /vercel/next.js/v15.1.11 (same major) | -| next14-nostore | next@14.2.35 | ✅ | ✅ | ✅ | ✅ | 761 | 82 | /vercel/next.js/v14.3.0-canary.87 (same major) | -| next15-connection | next@15.1.0 | ✅ | ✅ | ✅ | ✅ | 798 | 93 | /vercel/next.js/v15.1.11 (same major) | -| next15-after | next@15.1.0 | ✅ | ✅ | ❌ | ✅ | 879 | 92 | /vercel/next.js/v15.1.11 (same major) | -| rr6-json | react-router@6.26.2 | ✅ | ✅ | ❌ | ❌ | 796 | 99 | /websites/reactrouter (unversioned) | -| rr7-data | react-router@7.1.1 | ❌ | ✅ | ✅ | ✅ | 892 | 122 | /websites/reactrouter (unversioned) | -| rr6-defer | react-router@6.26.2 | ✅ | ✅ | ✅ | ✅ | 895 | 47 | /websites/reactrouter (unversioned) | -| rr6-future | react-router@6.26.2 | ✅ | ✅ | ✅ | ✅ | 815 | 52 | /websites/reactrouter (unversioned) | -| rr7-router | react-router@7.1.1 | ✅ | ✅ | ✅ | ✅ | 838 | 51 | /websites/reactrouter (unversioned) | -| pyd1-dict | pydantic@1.10.18 | ✅ | ✅ | ✅ | ❌ | 948 | 151 | /pydantic/pydantic (unversioned) | -| pyd2-dict | pydantic@2.9.2 | ❌ | ❌ | ✅ | ✅ | 909 | 245 | /pydantic/pydantic (unversioned) | -| pyd1-parse | pydantic@1.10.18 | ❌ | ❌ | ✅ | ❌ | 884 | 63 | /pydantic/pydantic (unversioned) | -| pyd2-parse | pydantic@2.9.2 | ❌ | ❌ | ✅ | ✅ | 843 | 72 | /pydantic/pydantic (unversioned) | -| pyd1-validator | pydantic@1.10.18 | ❌ | ✅ | ✅ | ❌ | 965 | 62 | /pydantic/pydantic (unversioned) | -| pyd2-validator | pydantic@2.9.2 | ❌ | ✅ | ✅ | ✅ | 764 | 67 | /pydantic/pydantic (unversioned) | -| pyd1-schema | pydantic@1.10.18 | ✅ | ✅ | ✅ | ❌ | 878 | 62 | /pydantic/pydantic (unversioned) | -| pyd2-schema | pydantic@2.9.2 | ✅ | ✅ | ✅ | ✅ | 817 | 72 | /pydantic/pydantic (unversioned) | -| pyd1-config | pydantic@1.10.18 | ❌ | ❌ | ❌ | ❌ | 945 | 59 | /pydantic/pydantic (unversioned) | -| pyd2-config | pydantic@2.9.2 | ✅ | ✅ | ✅ | ✅ | 934 | 71 | /pydantic/pydantic (unversioned) | -| axum07-path | axum@0.7.9 | ✅ | ✅ | ✅ | ❌ | 960 | 88 | /websites/rs_axum (unversioned) | -| axum08-path | axum@0.8.1 | ✅ | ✅ | ✅ | ✅ | 925 | 94 | /websites/rs_axum (unversioned) | -| axum07-extractor | axum@0.7.9 | ✅ | ✅ | ✅ | ❌ | 926 | 46 | /websites/rs_axum (unversioned) | -| axum08-optional | axum@0.8.1 | ✅ | ✅ | ✅ | ✅ | 898 | 42 | /websites/rs_axum (unversioned) | -| tokio-blocking | tokio@1.43.0 | ✅ | ✅ | ✅ | ✅ | 820 | 316 | /websites/rs_tokio_1_49_0 (unversioned) | -| tokio-select | tokio@1.43.0 | ❌ | ❌ | ❌ | ✅ | 969 | 60 | /websites/rs_tokio_1_49_0 (unversioned) | -| tokio-timeout | tokio@1.43.0 | ✅ | ✅ | ✅ | ✅ | 920 | 55 | /websites/rs_tokio_1_49_0 (unversioned) | -| tw3-css | tailwindcss@3.4.19 | ❌ | ❌ | ❌ | ❌ | 959 | 62 | /rails/tailwindcss-rails (unversioned) | -| tw4-css | tailwindcss@4.1.18 | ❌ | ❌ | ❌ | ✅ | 849 | 146 | /rails/tailwindcss-rails (unversioned) | -| tw3-theme | tailwindcss@3.4.19 | ✅ | ✅ | ✅ | ✅ | 1035 | 41 | /rails/tailwindcss-rails (unversioned) | -| tw4-theme | tailwindcss@4.1.18 | ❌ | ❌ | ✅ | ✅ | 1147 | 54 | /rails/tailwindcss-rails (unversioned) | -| eslint8-config | eslint@8.57.1 | ✅ | ✅ | ✅ | ✅ | 813 | 407 | /eslint/eslint/v8.57.1 (exact) | -| eslint9-config | eslint@9.39.5 | ✅ | ✅ | ✅ | ✅ | 741 | 360 | /eslint/eslint/v9.39.3 (same major) | -| eslint8-ignore | eslint@8.57.1 | ❌ | ❌ | ✅ | ✅ | 854 | 84 | /eslint/eslint/v8.57.1 (exact) | -| eslint9-ignore | eslint@9.39.5 | ❌ | ❌ | ✅ | ✅ | 825 | 83 | /eslint/eslint/v9.39.3 (same major) | -| prisma5-bytes | @prisma/client@5.22.0 | ❌ | ❌ | ❌ | ❌ | 878 | 78 | /websites/prisma_io (unversioned) | -| prisma6-bytes | @prisma/client@6.19.3 | ✅ | ✅ | ❌ | ✅ | 878 | 95 | /prisma/web (unversioned) | -| prisma5-fts | prisma@5.22.0 | ❌ | ❌ | ❌ | ❌ | 915 | 70 | /prisma/web (unversioned) | -| prisma6-fts | prisma@6.19.3 | ❌ | ❌ | ❌ | ✅ | 850 | 90 | /prisma/web (unversioned) | -| react18-action | react@18.3.1 | ✅ | ✅ | ❌ | ❌ | 951 | 129 | /reactjs/react.dev (unversioned) | -| react19-action | react@19.2.8 | ❌ | ✅ | ✅ | ✅ | 869 | 417 | /reactjs/react.dev (unversioned) | -| react18-use | react@18.3.1 | ❌ | ❌ | ✅ | ❌ | 804 | 48 | /reactjs/react.dev (unversioned) | -| react19-use | react@19.2.8 | ❌ | ❌ | ✅ | ✅ | 859 | 94 | /reactjs/react.dev (unversioned) | -| vite5-env | vite@5.4.21 | ✅ | ✅ | ✅ | ❌ | 907 | 164 | /vitejs/vite/v5.4.21 (exact) | -| vite6-env | vite@6.4.3 | ✅ | ✅ | ✅ | ✅ | 803 | 142 | /vitejs/vite (unversioned) | -| express4-wildcard | express@4.21.2 | ❌ | ❌ | ✅ | ❌ | 730 | 86 | /expressjs/express (unversioned) | -| express5-wildcard | express@5.2.1 | ❌ | ❌ | ✅ | ✅ | 944 | 87 | /expressjs/express/v5.2.0 (same major) | -| sa14-column | sqlalchemy@1.4.54 | ✅ | ✅ | ✅ | ❌ | 869 | 855 | /websites/sqlalchemy_en_20 (unversioned) | -| sa20-column | sqlalchemy@2.0.54 | ❌ | ✅ | ✅ | ✅ | 841 | 1036 | /websites/sqlalchemy_en_20 (unversioned) | -| sa14-base | sqlalchemy@1.4.54 | ✅ | ✅ | ✅ | ❌ | 831 | 152 | /websites/sqlalchemy_en_20 (unversioned) | -| sa20-base | sqlalchemy@2.0.54 | ✅ | ✅ | ✅ | ✅ | 860 | 167 | /websites/sqlalchemy_en_20 (unversioned) | -| dj42-dbdefault | django@4.2.30 | ❌ | ❌ | ❌ | ✅ | 852 | 483 | /django/django/4.2.21 (same major) | -| dj51-dbdefault | django@5.1.15 | ❌ | ❌ | ❌ | ✅ | 858 | 491 | /django/django (unversioned) | -| dj42-login | django@4.2.30 | ✅ | ✅ | ✅ | ✅ | 816 | 87 | /django/django/4.2.21 (same major) | -| dj51-login | django@5.1.15 | ✅ | ✅ | ✅ | ✅ | 855 | 92 | /django/django (unversioned) | -| fa088-lifespan | fastapi@0.88.0 | ❌ | ❌ | ❌ | ❌ | 833 | 181 | /websites/fastapi_tiangolo (unversioned) | -| fa0115-lifespan | fastapi@0.115.14 | ❌ | ❌ | ✅ | ✅ | 867 | 231 | /websites/fastapi_tiangolo (unversioned) | -| fa088-annotated | fastapi@0.88.0 | ❌ | ❌ | ✅ | ❌ | 875 | 69 | /websites/fastapi_tiangolo (unversioned) | -| fa0115-annotated | fastapi@0.115.14 | ✅ | ✅ | ✅ | ✅ | 838 | 70 | /websites/fastapi_tiangolo (unversioned) | -| next15-proxy | next@15.1.0 | ✅ | ✅ | ✅ | ❌ | 831 | 90 | /vercel/next.js/v15.1.11 (same major) | -| next16-proxy | next@16.3.6 | ✅ | ✅ | ✅ | ✅ | 869 | 630 | /vercel/next.js/v16.2.9 (same major) | +| zod3-strict | zod@3.23.8 | ✅ | ✅ | ✅ | ✅ | 1075 | 87 | /colinhacks/zod/v3.24.2 (same major) | +| zod4-strict | zod@4.1.5 | ✅ | ❌ | ✅ | ✅ | 1015 | 142 | /colinhacks/zod/v4.0.1 (same major) | +| zod3-email | zod@3.23.8 | ✅ | ✅ | ✅ | ✅ | 1119 | 47 | /colinhacks/zod/v3.24.2 (same major) | +| zod4-email | zod@4.1.5 | ✅ | ✅ | ✅ | ✅ | 903 | 60 | /colinhacks/zod/v4.0.1 (same major) | +| zod3-error | zod@3.23.8 | ✅ | ✅ | ✅ | ✅ | 991 | 47 | /colinhacks/zod/v3.24.2 (same major) | +| zod4-error | zod@4.1.5 | ✅ | ✅ | ✅ | ✅ | 991 | 60 | /colinhacks/zod/v4.0.1 (same major) | +| zod4-record | zod@4.1.5 | ❌ | ❌ | ✅ | ✅ | 946 | 55 | /colinhacks/zod/v4.0.1 (same major) | +| next14-cookies | next@14.2.35 | ❌ | ✅ | ❌ | ❌ | 926 | 395 | /vercel/next.js/v14.3.0-canary.87 (same major) | +| next15-cookies | next@15.1.0 | ✅ | ✅ | ✅ | ✅ | 866 | 448 | /vercel/next.js/v15.1.11 (same major) | +| next14-headers | next@14.2.35 | ✅ | ✅ | ✅ | ✅ | 896 | 94 | /vercel/next.js/v14.3.0-canary.87 (same major) | +| next15-headers | next@15.1.0 | ✅ | ✅ | ✅ | ✅ | 870 | 106 | /vercel/next.js/v15.1.11 (same major) | +| next14-nostore | next@14.2.35 | ✅ | ✅ | ✅ | ✅ | 843 | 97 | /vercel/next.js/v14.3.0-canary.87 (same major) | +| next15-connection | next@15.1.0 | ✅ | ✅ | ✅ | ✅ | 754 | 108 | /vercel/next.js/v15.1.11 (same major) | +| next15-after | next@15.1.0 | ✅ | ✅ | ✅ | ✅ | 827 | 100 | /vercel/next.js/v15.1.11 (same major) | +| rr6-json | react-router@6.26.2 | ✅ | ✅ | ❌ | ❌ | 796 | 120 | /websites/reactrouter (unversioned) | +| rr7-data | react-router@7.1.1 | ✅ | ❌ | ✅ | ✅ | 876 | 144 | /websites/reactrouter (unversioned) | +| rr6-defer | react-router@6.26.2 | ✅ | ✅ | ✅ | ✅ | 872 | 50 | /websites/reactrouter (unversioned) | +| rr6-future | react-router@6.26.2 | ✅ | ✅ | ✅ | ✅ | 755 | 56 | /websites/reactrouter (unversioned) | +| rr7-router | react-router@7.1.1 | ✅ | ✅ | ✅ | ✅ | 807 | 58 | /websites/reactrouter (unversioned) | +| pyd1-dict | pydantic@1.10.18 | ✅ | ✅ | ✅ | ❌ | 846 | 157 | /pydantic/pydantic (unversioned) | +| pyd2-dict | pydantic@2.9.2 | ❌ | ✅ | ✅ | ✅ | 952 | 279 | /pydantic/pydantic (unversioned) | +| pyd1-parse | pydantic@1.10.18 | ❌ | ❌ | ✅ | ❌ | 845 | 64 | /pydantic/pydantic (unversioned) | +| pyd2-parse | pydantic@2.9.2 | ❌ | ✅ | ✅ | ✅ | 825 | 76 | /pydantic/pydantic (unversioned) | +| pyd1-validator | pydantic@1.10.18 | ✅ | ✅ | ✅ | ❌ | 961 | 62 | /pydantic/pydantic (unversioned) | +| pyd2-validator | pydantic@2.9.2 | ❌ | ✅ | ✅ | ✅ | 775 | 77 | /pydantic/pydantic (unversioned) | +| pyd1-schema | pydantic@1.10.18 | ✅ | ✅ | ✅ | ❌ | 818 | 68 | /pydantic/pydantic (unversioned) | +| pyd2-schema | pydantic@2.9.2 | ✅ | ✅ | ✅ | ✅ | 880 | 79 | /pydantic/pydantic (unversioned) | +| pyd1-config | pydantic@1.10.18 | ❌ | ❌ | ❌ | ❌ | 923 | 64 | /pydantic/pydantic (unversioned) | +| pyd2-config | pydantic@2.9.2 | ✅ | ✅ | ✅ | ✅ | 952 | 80 | /pydantic/pydantic (unversioned) | +| axum07-path | axum@0.7.9 | ✅ | ✅ | ✅ | ❌ | 1020 | 103 | /websites/rs_axum (unversioned) | +| axum08-path | axum@0.8.1 | ✅ | ✅ | ✅ | ✅ | 962 | 97 | /websites/rs_axum (unversioned) | +| axum07-extractor | axum@0.7.9 | ✅ | ✅ | ✅ | ❌ | 912 | 46 | /websites/rs_axum (unversioned) | +| axum08-optional | axum@0.8.1 | ✅ | ✅ | ✅ | ✅ | 891 | 41 | /websites/rs_axum (unversioned) | +| tokio-blocking | tokio@1.43.0 | ✅ | ✅ | ✅ | ✅ | 823 | 363 | /websites/rs_tokio_1_49_0 (unversioned) | +| tokio-select | tokio@1.43.0 | ❌ | ❌ | ✅ | ✅ | 962 | 69 | /websites/rs_tokio_1_49_0 (unversioned) | +| tokio-timeout | tokio@1.43.0 | ✅ | ✅ | ✅ | ✅ | 941 | 63 | /websites/rs_tokio_1_49_0 (unversioned) | +| tw3-css | tailwindcss@3.4.19 | ❌ | ❌ | ✅ | ❌ | 971 | 170 | /rails/tailwindcss-rails (unversioned) | +| tw4-css | tailwindcss@4.1.18 | ❌ | ❌ | ✅ | ✅ | 979 | 193 | /rails/tailwindcss-rails (unversioned) | +| tw3-theme | tailwindcss@3.4.19 | ✅ | ✅ | ✅ | ✅ | 1003 | 63 | /rails/tailwindcss-rails (unversioned) | +| tw4-theme | tailwindcss@4.1.18 | ❌ | ❌ | ✅ | ✅ | 1144 | 64 | /rails/tailwindcss-rails (unversioned) | +| eslint8-config | eslint@8.57.1 | ✅ | ✅ | ✅ | ✅ | 796 | 474 | /eslint/eslint/v8.57.1 (exact) | +| eslint9-config | eslint@9.39.5 | ✅ | ✅ | ✅ | ✅ | 720 | 417 | /eslint/eslint/v9.39.3 (same major) | +| eslint8-ignore | eslint@8.57.1 | ❌ | ❌ | ✅ | ✅ | 854 | 88 | /eslint/eslint/v8.57.1 (exact) | +| eslint9-ignore | eslint@9.39.5 | ❌ | ❌ | ✅ | ✅ | 849 | 99 | /eslint/eslint/v9.39.3 (same major) | +| prisma5-bytes | @prisma/client@5.22.0 | ❌ | ❌ | ❌ | ❌ | 846 | 385 | /websites/prisma_io (unversioned) | +| prisma6-bytes | @prisma/client@6.19.3 | ✅ | ✅ | ✅ | ✅ | 848 | 438 | /prisma/web (unversioned) | +| prisma5-fts | prisma@5.22.0 | ❌ | ❌ | ✅ | ❌ | 849 | 385 | /prisma/web (unversioned) | +| prisma6-fts | prisma@6.19.3 | ❌ | ❌ | ✅ | ✅ | 846 | 471 | /prisma/web (unversioned) | +| react18-action | react@18.3.1 | ✅ | ❌ | ❌ | ❌ | 832 | 409 | /reactjs/react.dev (unversioned) | +| react19-action | react@19.2.8 | ❌ | ✅ | ✅ | ✅ | 872 | 464 | /reactjs/react.dev (unversioned) | +| react18-use | react@18.3.1 | ❌ | ❌ | ✅ | ❌ | 823 | 99 | /reactjs/react.dev (unversioned) | +| react19-use | react@19.2.8 | ❌ | ❌ | ✅ | ✅ | 854 | 104 | /reactjs/react.dev (unversioned) | +| vite5-env | vite@5.4.21 | ✅ | ✅ | ✅ | ❌ | 897 | 186 | /vitejs/vite/v5.4.21 (exact) | +| vite6-env | vite@6.4.3 | ✅ | ✅ | ✅ | ✅ | 839 | 165 | /vitejs/vite (unversioned) | +| express4-wildcard | express@4.21.2 | ❌ | ❌ | ✅ | ❌ | 813 | 96 | /expressjs/express (unversioned) | +| express5-wildcard | express@5.2.1 | ❌ | ❌ | ✅ | ✅ | 932 | 97 | /expressjs/express/v5.2.0 (same major) | +| sa14-column | sqlalchemy@1.4.54 | ✅ | ✅ | ✅ | ❌ | 852 | 978 | /websites/sqlalchemy_en_20 (unversioned) | +| sa20-column | sqlalchemy@2.0.54 | ❌ | ✅ | ✅ | ✅ | 815 | 1167 | /websites/sqlalchemy_en_20 (unversioned) | +| sa14-base | sqlalchemy@1.4.54 | ✅ | ✅ | ✅ | ❌ | 828 | 166 | /websites/sqlalchemy_en_20 (unversioned) | +| sa20-base | sqlalchemy@2.0.54 | ✅ | ✅ | ✅ | ✅ | 863 | 190 | /websites/sqlalchemy_en_20 (unversioned) | +| dj42-dbdefault | django@4.2.30 | ❌ | ❌ | ✅ | ✅ | 812 | 1185 | /django/django/4.2.21 (same major) | +| dj51-dbdefault | django@5.1.15 | ❌ | ✅ | ✅ | ✅ | 802 | 1214 | /django/django (unversioned) | +| dj42-login | django@4.2.30 | ✅ | ✅ | ✅ | ✅ | 790 | 206 | /django/django/4.2.21 (same major) | +| dj51-login | django@5.1.15 | ✅ | ✅ | ✅ | ✅ | 736 | 207 | /django/django (unversioned) | +| fa088-lifespan | fastapi@0.88.0 | ❌ | ❌ | ❌ | ❌ | 873 | 203 | /websites/fastapi_tiangolo (unversioned) | +| fa0115-lifespan | fastapi@0.115.14 | ❌ | ❌ | ✅ | ✅ | 828 | 257 | /websites/fastapi_tiangolo (unversioned) | +| fa088-annotated | fastapi@0.88.0 | ❌ | ❌ | ✅ | ❌ | 865 | 76 | /websites/fastapi_tiangolo (unversioned) | +| fa0115-annotated | fastapi@0.115.14 | ✅ | ✅ | ✅ | ✅ | 766 | 82 | /websites/fastapi_tiangolo (unversioned) | +| next15-proxy | next@15.1.0 | ✅ | ✅ | ✅ | ❌ | 797 | 104 | /vercel/next.js/v15.1.11 (same major) | +| next16-proxy | next@16.3.6 | ✅ | ✅ | ✅ | ✅ | 816 | 718 | /vercel/next.js/v16.2.9 (same major) | +| next14-params | next@14.2.35 | ✅ | ✅ | ✅ | ✅ | 881 | 97 | /vercel/next.js/v14.3.0-canary.87 (same major) | +| next15-params | next@15.1.0 | ❌ | ❌ | ✅ | ✅ | 880 | 105 | /vercel/next.js/v15.1.11 (same major) | +| tw3-dark | tailwindcss@3.4.19 | ✅ | ✅ | ✅ | ✅ | 896 | 66 | /erimicel/select2-tailwindcss-theme (unversioned) | +| tw4-dark | tailwindcss@4.1.18 | ❌ | ❌ | ✅ | ❌ | 874 | 68 | /erimicel/select2-tailwindcss-theme (unversioned) | +| react18-ref | react@18.3.1 | ✅ | ✅ | ✅ | ✅ | 899 | 101 | /reactjs/react.dev (unversioned) | +| react19-ref | react@19.2.8 | ❌ | ❌ | ✅ | ✅ | 796 | 111 | /reactjs/react.dev (unversioned) | +| rr6-types | react-router@6.26.2 | ✅ | ✅ | ✅ | ❌ | 790 | 57 | /websites/reactrouter (unversioned) | +| rr7-types | react-router@7.1.1 | ❌ | ❌ | ✅ | ✅ | 851 | 58 | /websites/reactrouter (unversioned) | +| eslint8-globals | eslint@8.57.1 | ❌ | ❌ | ❌ | ✅ | 768 | 95 | /eslint/eslint/v8.57.1 (exact) | +| eslint9-globals | eslint@9.39.5 | ❌ | ❌ | ✅ | ✅ | 741 | 96 | /eslint/eslint/v9.39.3 (same major) | +| dj51-generated | django@5.1.15 | ❌ | ❌ | ✅ | ✅ | 750 | 218 | /django/django (unversioned) | +| fa0115-querymodel | fastapi@0.115.14 | ❌ | ❌ | ✅ | ✅ | 887 | 85 | /websites/fastapi_tiangolo (unversioned) | +| tokio-channel | tokio@1.43.0 | ✅ | ✅ | ✅ | ✅ | 881 | 71 | /websites/rs_tokio_tokio (unversioned) | +| zod3-datetime | zod@3.23.8 | ✅ | ✅ | ✅ | ❌ | 1162 | 44 | /colinhacks/zod/v3.24.2 (same major) | +| zod4-datetime | zod@4.1.5 | ✅ | ✅ | ✅ | ✅ | 1158 | 61 | /colinhacks/zod/v4.0.1 (same major) | +| pyd1-frozen | pydantic@1.10.18 | ✅ | ❌ | ✅ | ❌ | 930 | 69 | /pydantic/pydantic (unversioned) | +| pyd2-frozen | pydantic@2.9.2 | ❌ | ❌ | ✅ | ✅ | 884 | 82 | /pydantic/pydantic (unversioned) | +| next15-form (held-out) | next@15.1.0 | ❌ | ❌ | ✅ | ✅ | 898 | 103 | /vercel/next.js/v15.1.11 (same major) | +| next16-cache (held-out) | next@16.3.6 | ✅ | ✅ | ✅ | ✅ | 856 | 142 | /vercel/next.js/v16.2.9 (same major) | +| zod3-meta (held-out) | zod@3.23.8 | ✅ | ✅ | ✅ | ✅ | 975 | 45 | /colinhacks/zod/v3.24.2 (same major) | +| zod4-meta (held-out) | zod@4.1.5 | ❌ | ❌ | ✅ | ✅ | 888 | 55 | /colinhacks/zod/v4.0.1 (same major) | +| pyd2-computed (held-out) | pydantic@2.9.2 | ✅ | ✅ | ✅ | ✅ | 892 | 82 | /pydantic/pydantic (unversioned) | +| pyd1-json (held-out) | pydantic@1.10.18 | ❌ | ❌ | ✅ | ❌ | 938 | 70 | /pydantic/pydantic (unversioned) | +| pyd2-json (held-out) | pydantic@2.9.2 | ✅ | ✅ | ✅ | ✅ | 872 | 79 | /pydantic/pydantic (unversioned) | +| sa20-dataclass (held-out) | sqlalchemy@2.0.54 | ✅ | ✅ | ✅ | ✅ | 867 | 193 | /websites/sqlalchemy_en_20 (unversioned) | +| eslint8-plugins (held-out) | eslint@8.57.1 | ❌ | ❌ | ✅ | ✅ | 784 | 90 | /eslint/eslint/v8.57.1 (exact) | +| eslint9-plugins (held-out) | eslint@9.39.5 | ❌ | ❌ | ❌ | ✅ | 788 | 101 | /eslint/eslint/v9.39.3 (same major) | +| vite6-envapi (held-out) | vite@6.4.3 | ✅ | ❌ | ✅ | ✅ | 673 | 62 | /vitejs/vite (unversioned) | +| react18-context (held-out) | react@18.3.1 | ✅ | ❌ | ✅ | ❌ | 823 | 98 | /reactjs/react.dev (unversioned) | +| react19-context (held-out) | react@19.2.8 | ❌ | ❌ | ✅ | ✅ | 812 | 104 | /reactjs/react.dev (unversioned) | +| rr7-routesfile (held-out) | react-router@7.1.1 | ❌ | ❌ | ✅ | ✅ | 898 | 55 | /websites/reactrouter (unversioned) | +| dj51-facets (held-out) | django@5.1.15 | ❌ | ❌ | ✅ | ✅ | 769 | 209 | /django/django (unversioned) | +| tw3-source (held-out) | tailwindcss@3.4.19 | ❌ | ❌ | ✅ | ✅ | 860 | 62 | /rails/tailwindcss-rails (unversioned) | +| tw4-source (held-out) | tailwindcss@4.1.18 | ❌ | ❌ | ❌ | ❌ | 939 | 67 | /rails/tailwindcss-rails (unversioned) | +| tokio-interval (held-out) | tokio@1.43.0 | ✅ | ✅ | ✅ | ✅ | 890 | 65 | /websites/rs_tokio_tokio (unversioned) | Index build per project (cold cache, all direct deps, package files): -axum07 345 ms, axum08 401 ms, django42 473 ms, django51 480 ms, eslint8 182 ms, eslint9 80 ms, express4 72 ms, express5 48 ms, fastapi0115 83 ms, fastapi088 71 ms, next14 378 ms, next15 603 ms, next16 868 ms, prisma5 92 ms, prisma6 123 ms, pydantic1 98 ms, pydantic2 176 ms, react18 361 ms, react19 659 ms, rr6 279 ms, rr7 524 ms, sqlalchemy14 466 ms, sqlalchemy20 604 ms, tailwind3 50 ms, tailwind4 46 ms, vite5 91 ms, vite6 90 ms, zod3 65 ms, zod4 106 ms +axum07 391 ms, axum08 382 ms, django42 532 ms, django51 534 ms, eslint8 185 ms, eslint9 78 ms, express4 81 ms, express5 48 ms, fastapi0115 89 ms, fastapi088 69 ms, next14 391 ms, next15 576 ms, next16 1070 ms, prisma5 97 ms, prisma6 125 ms, pydantic1 106 ms, pydantic2 184 ms, react18 370 ms, react19 662 ms, rr6 290 ms, rr7 515 ms, sqlalchemy14 500 ms, sqlalchemy20 650 ms, tailwind3 46 ms, tailwind4 44 ms, vite5 93 ms, vite6 100 ms, zod3 73 ms, zod4 115 ms `lockdocs fetch` per project (one-time; upstream docs from GitHub at the version tag): -- axum07: 1636 ms (axum@0.7.9 2 files, tokio@1.41.1 1 files) -- axum08: 2101 ms (tokio@1.43.0 1 files) -- django42: 4897 ms (django@4.2.30 601 files) -- django51: 4876 ms (django@5.1.15 629 files) -- eslint8: 3197 ms (eslint@8.57.1 409 files) -- eslint9: 3391 ms (eslint@9.39.5 435 files) -- express4: 2984 ms (@types/express@4.17.25 6 files, express@4.21.2 10 files) -- express5: 3046 ms (@types/express@5.0.6 38 files, express@5.2.1 40 files) -- fastapi0115: 2482 ms (fastapi@0.115.14 180 files) -- fastapi088: 2335 ms (fastapi@0.88.0 113 files) -- next14: 4976 ms (next@14.2.35 318 files, react@18.3.1 3 files, react-dom@18.3.1 3 files) -- next15: 3429 ms (next@15.1.0 365 files, react@19.0.0 184 files, react-dom@19.0.0 184 files) -- next16: 4143 ms (next@16.3.6 458 files, react@19.2.8 183 files, react-dom@19.2.8 183 files) -- prisma5: 1697 ms (@prisma/client@5.22.0 2 files, prisma@5.22.0 2 files) -- prisma6: 1739 ms (@prisma/client@6.19.3 2 files, prisma@6.19.3 2 files) -- pydantic1: 1635 ms (pydantic@1.10.18 176 files) -- pydantic2: 1223 ms (pydantic@2.9.2 80 files) -- react18: 2396 ms (react@18.3.1 3 files, react-dom@18.3.1 3 files) -- react19: 3314 ms (@types/react@19.2.18 180 files, react@19.2.8 183 files, react-dom@19.2.8 183 files) -- rr6: 2184 ms (react@18.3.1 3 files, react-dom@18.3.1 3 files, react-router@6.26.2 118 files, react-router-dom@6.26.2 118 files) -- rr7: 2112 ms (react@19.0.0 184 files, react-dom@19.0.0 184 files, react-router@7.1.1 68 files) -- sqlalchemy14: 3270 ms (sqlalchemy@1.4.54 179 files) -- sqlalchemy20: 3201 ms (sqlalchemy@2.0.54 198 files) -- tailwind3: 725 ms (tailwindcss@3.4.19 2 files) -- tailwind4: 2472 ms (tailwindcss@4.1.18 200 files) -- vite5: 1379 ms (vite@5.4.21 37 files) -- vite6: 1511 ms (vite@6.4.3 47 files) -- zod3: 612 ms (zod@3.23.8 4 files) -- zod4: 1219 ms (zod@4.1.5 18 files) +- axum07: 2469 ms (axum@0.7.9 2 files, tokio@1.41.1 18 files) +- axum08: 2192 ms (tokio@1.43.0 18 files) +- django42: 4160 ms (django@4.2.30 601 files) +- django51: 4787 ms (django@5.1.15 629 files) +- eslint8: 3476 ms (eslint@8.57.1 409 files) +- eslint9: 3330 ms (eslint@9.39.5 435 files) +- express4: 3582 ms (@types/express@4.17.25 6 files, express@4.21.2 10 files) +- express5: 2913 ms (@types/express@5.0.6 38 files, express@5.2.1 40 files) +- fastapi0115: 2264 ms (fastapi@0.115.14 180 files) +- fastapi088: 2594 ms (fastapi@0.88.0 113 files) +- next14: 3699 ms (next@14.2.35 318 files, react@18.3.1 140 files, react-dom@18.3.1 140 files) +- next15: 4137 ms (next@15.1.0 365 files, react@19.0.0 184 files, react-dom@19.0.0 184 files) +- next16: 3730 ms (next@16.3.6 458 files, react@19.2.8 183 files, react-dom@19.2.8 183 files) +- prisma5: 6014 ms (@prisma/client@5.22.0 236 files, prisma@5.22.0 236 files) +- prisma6: 3632 ms (@prisma/client@6.19.3 247 files, prisma@6.19.3 247 files) +- pydantic1: 1757 ms (pydantic@1.10.18 176 files) +- pydantic2: 1177 ms (pydantic@2.9.2 80 files) +- react18: 2923 ms (@types/react@18.3.31 137 files, react@18.3.1 140 files, react-dom@18.3.1 140 files) +- react19: 2912 ms (@types/react@19.2.18 180 files, react@19.2.8 183 files, react-dom@19.2.8 183 files) +- rr6: 2148 ms (react@18.3.1 140 files, react-dom@18.3.1 140 files, react-router@6.26.2 118 files, react-router-dom@6.26.2 118 files) +- rr7: 2033 ms (react@19.0.0 184 files, react-dom@19.0.0 184 files, react-router@7.1.1 68 files) +- sqlalchemy14: 2995 ms (sqlalchemy@1.4.54 179 files) +- sqlalchemy20: 3057 ms (sqlalchemy@2.0.54 198 files) +- tailwind3: 3464 ms (tailwindcss@3.4.19 192 files) +- tailwind4: 7799 ms (tailwindcss@4.1.18 224 files) +- vite5: 1136 ms (vite@5.4.21 37 files) +- vite6: 1367 ms (vite@6.4.3 47 files) +- zod3: 630 ms (zod@3.23.8 4 files) +- zod4: 1337 ms (zod@4.1.5 18 files) ## Reading the results -- **lockdocs is ahead overall (55/70 vs 49/70), on older majors by a wide margin (24/33 vs 12/33), on tokens (875 vs 908 median) and on latency (87 ms vs 2,011 ms).** Context7 serves one or a few indexed versions per library, so questions about the version you actually pinned often get the newest API. -- **Context7 is ahead on the newest majors (34/34 vs 29/34) and on tokio (3/3 vs 2/3).** lockdocs misses where the answer lives only in a docs website that tracks a different major than the pinned one (Prisma 6: the Prisma docs now describe a later major), and on a few ranking misses (Next.js `after`, Tailwind 4's `@import "tailwindcss"`, Django `db_default`, tokio `select!` for "whichever finishes first"). -- **Configurations matter.** Keyword-only on package files: 41/70. Adding the local embedding model: 46/70. Adding `lockdocs fetch` (upstream docs at each version's git tag, plus docs-site repositories when the pinned major is the current one): 55/70. +- **lockdocs is ahead overall (96/105 vs 77/105) and on older majors by 2x (38/45 vs 19/45), ties Context7 on the newest majors (53/55 each) and on tokio (5/5 each), and is ahead on the held-out questions (16/18 vs 15/18).** It also uses fewer tokens (866 vs 908 median) and answers in 97 ms instead of 2,583 ms. +- **Where each still misses.** lockdocs: seven older-major questions (among them Next.js 14 `cookies()`, React Router 6 `json()`, FastAPI 0.88 `on_event`, Prisma 5 `Buffer`, ESLint 8 `env`), and two held-out newer-major questions (ESLint 9 plugins, Tailwind 4 `@source`). Context7 mostly answers older-major questions with the newest API, and two of its Tailwind answers came from unrelated libraries its search ranked first. +- **Configurations matter.** Keyword-only on package files: 59/105. Adding the local embedding model: 60/105. Adding `lockdocs fetch` (upstream docs at each version's git tag, plus docs-site repositories for your major): 96/105. - Latency for lockdocs is a fresh CLI process per question, including loading the embedding model; the MCP server keeps it loaded. -- Context7 answers for questions unchanged since the previous run on the same pinned version were reused from that run (disclosed in the results line) to stay within the anonymous quota. +- Context7 answers for questions whose wording, grading and pinned version are unchanged since the previous run were reused from that run (disclosed in the results line) to stay within the anonymous quota. - Contributions of new version-sensitive questions are welcome. diff --git a/docs/guide/how-it-works.md b/docs/guide/how-it-works.md index 357f047..2be4f67 100644 --- a/docs/guide/how-it-works.md +++ b/docs/guide/how-it-works.md @@ -3,8 +3,8 @@ 1. **Resolve.** Every lockfile in the project directory, and in monorepo roots above it for ecosystems not found yet, becomes a list of exact `(ecosystem, name, version)` triples. Direct dependencies are marked. 2. **Locate.** Each package's files are found on disk (see [Ecosystems](./ecosystems)). If the installed version differs from the lockfile, the answer says so and shows the installed one; with fetching enabled it reads the pinned version instead. 3. **Extract.** Markdown and reStructuredText are split into heading-scoped sections (long ones at paragraph boundaries, never inside code fences). HTML headings in MDX (`

@import

`) count as headings, and `export const title` names the page. Docs pages written as React components (Tailwind's installation guides) become sections too: step titles are headings, JSX text is prose, and `code:` strings are code blocks. Source files are parsed with tree-sitter (TypeScript, Python, Rust, Go) into symbols with a signature (the declaration without its body), a cleaned doc comment, a qualified path and a line number. -4. **Index.** Each entry becomes weighted terms: names and headings count most, doc prose next, signatures and code examples least. The tokenizer keeps identifiers whole and also splits camelCase, snake_case and digits; a light stemmer and a short programming synonym table (dict/dump, parse/validate, error/exception, ...) bridge wording. The index is cached on disk per package, version and source location, so a version is indexed once. -5. **Rank.** BM25 over the full text, BM25 over just the heading (or name) and first sentence, so a long body does not bury what an entry says it is, and embedding similarity (a static model2vec model: a text's vector is the mean of its WordPiece token vectors, so embedding every symbol of Next.js takes well under a second), each normalized to its best hit. Then signals that matter for docs: entries whose name or heading contains a query word, identifiers written in the question (`model_dump`, `z.object`) and plain question words that name a documented top-level API of the package ("run code *after* the response" in Next.js, which exports `after`), changelog sections only when the question is about changes, and lower weight for private modules, bundled tooling and legacy copies. Deprecation notes among the top results ("use `model_validate` instead") lift the API they point to. -6. **Pack.** Results are added in rank order until the token budget is spent (the last one trimmed at a line boundary), each with a `package@version path:line` citation. +4. **Index.** Each entry becomes weighted terms: names and headings count most, doc prose next, signatures and code examples least. The tokenizer keeps identifiers whole and also splits camelCase, snake_case and digits; a light stemmer (plurals, -ing, -ed, -ation/-ate, -ability/-able) and a short programming synonym table (dict/dump, parse/validate, parameter/param, JavaScript/JS, ...) bridge wording. The index is cached on disk per package, version and source location, so a version is indexed once. +5. **Rank.** BM25 over the full text, BM25 over just the heading (or name) and first sentence, so a long body does not bury what an entry says it is, and embedding similarity (a static model2vec model: a text's vector is the mean of its WordPiece token vectors, so embedding every symbol of Next.js takes well under a second), each normalized to its best hit. Then signals that matter for docs: entries whose name or heading contains a query word, identifiers written in the question (`model_dump`, `z.object`) and plain question words that name a documented top-level API of the package ("run code *after* the response" in Next.js, which exports `after`), changelog sections only when the question is about changes, and lower weight for private modules, bundled tooling, legacy copies, upgrade guides to an older major than yours ("Migrating to v6" in ESLint 9) and pages titled "(Deprecated)". Generic headings such as Parameters, Returns or Examples take their topic from the heading above them. Deprecation notes among the top results ("use `model_validate` instead") lift the API they point to. +6. **Pack.** The top result also quotes the first paragraph of its page and parent section (with the short list or code block that follows), when they add something: a deprecation notice at the top of the page, or the setup a subsection builds on. Results are added in rank order until the token budget is spent (the last one trimmed at a line boundary), each with a `package@version path:line` citation. `api` is exact rather than ranked: it finds entries whose name matches the last segment of the symbol, scores qualifiers (`Router` in `axum::Router::route`), follows re-exports, and adds overloads, members and other matches. diff --git a/docs/public/bench-results.json b/docs/public/bench-results.json index e801f94..a0b9a14 100644 --- a/docs/public/bench-results.json +++ b/docs/public/bench-results.json @@ -2,13 +2,13 @@ "tokenizer": "tiktoken o200k_base", "index": { "axum07": { - "ms": 271, + "ms": 391, "report": { "missing": [], - "ms": 259, + "ms": 378, "packages": [ { - "build_ms": 149, + "build_ms": 164, "ecosystem": "cargo", "package": "axum@0.7.9", "sections": 262, @@ -16,7 +16,7 @@ "symbols": 236 }, { - "build_ms": 218, + "build_ms": 319, "ecosystem": "cargo", "package": "tokio@1.41.1", "sections": 551, @@ -27,13 +27,13 @@ } }, "axum08": { - "ms": 285, + "ms": 382, "report": { "missing": [], - "ms": 274, + "ms": 370, "packages": [ { - "build_ms": 137, + "build_ms": 170, "ecosystem": "cargo", "package": "axum@0.8.1", "sections": 272, @@ -41,7 +41,7 @@ "symbols": 264 }, { - "build_ms": 236, + "build_ms": 324, "ecosystem": "cargo", "package": "tokio@1.43.0", "sections": 561, @@ -52,13 +52,13 @@ } }, "django42": { - "ms": 375, + "ms": 532, "report": { "missing": [], - "ms": 359, + "ms": 516, "packages": [ { - "build_ms": 309, + "build_ms": 451, "ecosystem": "pypi", "package": "django@4.2.30", "sections": 159, @@ -69,13 +69,13 @@ } }, "django51": { - "ms": 371, + "ms": 534, "report": { "missing": [], - "ms": 353, + "ms": 517, "packages": [ { - "build_ms": 303, + "build_ms": 453, "ecosystem": "pypi", "package": "django@5.1.15", "sections": 159, @@ -86,13 +86,13 @@ } }, "eslint8": { - "ms": 130, + "ms": 185, "report": { "missing": [], - "ms": 126, + "ms": 179, "packages": [ { - "build_ms": 97, + "build_ms": 144, "ecosystem": "npm", "package": "eslint@8.57.1", "sections": 27, @@ -103,13 +103,13 @@ } }, "eslint9": { - "ms": 59, + "ms": 78, "report": { "missing": [], - "ms": 55, + "ms": 73, "packages": [ { - "build_ms": 24, + "build_ms": 39, "ecosystem": "npm", "package": "eslint@9.39.5", "sections": 28, @@ -120,13 +120,13 @@ } }, "express4": { - "ms": 53, + "ms": 81, "report": { "missing": [], - "ms": 49, + "ms": 77, "packages": [ { - "build_ms": 4, + "build_ms": 2, "ecosystem": "npm", "package": "@types/express@4.17.25", "sections": 5, @@ -134,7 +134,7 @@ "symbols": 35 }, { - "build_ms": 16, + "build_ms": 22, "ecosystem": "npm", "package": "express@4.21.2", "sections": 276, @@ -145,10 +145,10 @@ } }, "express5": { - "ms": 41, + "ms": 48, "report": { "missing": [], - "ms": 37, + "ms": 44, "packages": [ { "build_ms": 4, @@ -159,7 +159,7 @@ "symbols": 34 }, { - "build_ms": 5, + "build_ms": 7, "ecosystem": "npm", "package": "express@5.2.1", "sections": 18, @@ -170,13 +170,13 @@ } }, "fastapi0115": { - "ms": 65, + "ms": 89, "report": { "missing": [], - "ms": 61, + "ms": 85, "packages": [ { - "build_ms": 24, + "build_ms": 36, "ecosystem": "pypi", "package": "fastapi@0.115.14", "sections": 27, @@ -187,13 +187,13 @@ } }, "fastapi088": { - "ms": 54, + "ms": 69, "report": { "missing": [], - "ms": 52, + "ms": 66, "packages": [ { - "build_ms": 14, + "build_ms": 20, "ecosystem": "pypi", "package": "fastapi@0.88.0", "sections": 24, @@ -204,13 +204,13 @@ } }, "next14": { - "ms": 293, + "ms": 391, "report": { "missing": [], - "ms": 283, + "ms": 379, "packages": [ { - "build_ms": 246, + "build_ms": 331, "ecosystem": "npm", "package": "next@14.2.35", "sections": 10, @@ -218,7 +218,7 @@ "symbols": 4904 }, { - "build_ms": 29, + "build_ms": 43, "ecosystem": "npm", "package": "react@18.3.1", "sections": 4, @@ -226,7 +226,7 @@ "symbols": 0 }, { - "build_ms": 169, + "build_ms": 235, "ecosystem": "npm", "package": "react-dom@18.3.1", "sections": 7, @@ -237,13 +237,13 @@ } }, "next15": { - "ms": 429, + "ms": 576, "report": { "missing": [], - "ms": 417, + "ms": 563, "packages": [ { - "build_ms": 314, + "build_ms": 426, "ecosystem": "npm", "package": "next@15.1.0", "sections": 9, @@ -251,7 +251,7 @@ "symbols": 5585 }, { - "build_ms": 35, + "build_ms": 337, "ecosystem": "npm", "package": "react@19.0.0", "sections": 4, @@ -259,7 +259,7 @@ "symbols": 0 }, { - "build_ms": 369, + "build_ms": 506, "ecosystem": "npm", "package": "react-dom@19.0.0", "sections": 7, @@ -270,13 +270,13 @@ } }, "next16": { - "ms": 713, + "ms": 1070, "report": { "missing": [], - "ms": 681, + "ms": 1038, "packages": [ { - "build_ms": 605, + "build_ms": 965, "ecosystem": "npm", "package": "next@16.3.6", "sections": 3598, @@ -284,7 +284,7 @@ "symbols": 8354 }, { - "build_ms": 25, + "build_ms": 455, "ecosystem": "npm", "package": "react@19.2.8", "sections": 4, @@ -292,7 +292,7 @@ "symbols": 0 }, { - "build_ms": 424, + "build_ms": 711, "ecosystem": "npm", "package": "react-dom@19.2.8", "sections": 7, @@ -303,13 +303,13 @@ } }, "prisma5": { - "ms": 73, + "ms": 97, "report": { "missing": [], - "ms": 69, + "ms": 92, "packages": [ { - "build_ms": 39, + "build_ms": 54, "ecosystem": "npm", "package": "@prisma/client@5.22.0", "sections": 4, @@ -317,7 +317,7 @@ "symbols": 814 }, { - "build_ms": 34, + "build_ms": 51, "ecosystem": "npm", "package": "prisma@5.22.0", "sections": 14, @@ -328,13 +328,13 @@ } }, "prisma6": { - "ms": 94, + "ms": 125, "report": { "missing": [], - "ms": 89, + "ms": 119, "packages": [ { - "build_ms": 55, + "build_ms": 84, "ecosystem": "npm", "package": "@prisma/client@6.19.3", "sections": 4, @@ -342,7 +342,7 @@ "symbols": 851 }, { - "build_ms": 59, + "build_ms": 79, "ecosystem": "npm", "package": "prisma@6.19.3", "sections": 14, @@ -353,13 +353,13 @@ } }, "pydantic1": { - "ms": 80, + "ms": 106, "report": { "missing": [], - "ms": 76, + "ms": 101, "packages": [ { - "build_ms": 38, + "build_ms": 56, "ecosystem": "pypi", "package": "pydantic@1.10.18", "sections": 135, @@ -370,13 +370,13 @@ } }, "pydantic2": { - "ms": 135, + "ms": 184, "report": { "missing": [], - "ms": 129, + "ms": 177, "packages": [ { - "build_ms": 89, + "build_ms": 129, "ecosystem": "pypi", "package": "pydantic@2.9.2", "sections": 191, @@ -387,13 +387,13 @@ } }, "react18": { - "ms": 266, + "ms": 370, "report": { "missing": [], - "ms": 257, + "ms": 359, "packages": [ { - "build_ms": 101, + "build_ms": 287, "ecosystem": "npm", "package": "@types/react@18.3.31", "sections": 5, @@ -401,7 +401,7 @@ "symbols": 1853 }, { - "build_ms": 14, + "build_ms": 18, "ecosystem": "npm", "package": "@types/react-dom@18.3.7", "sections": 5, @@ -409,7 +409,7 @@ "symbols": 295 }, { - "build_ms": 118, + "build_ms": 182, "ecosystem": "npm", "package": "react@18.3.1", "sections": 4, @@ -417,7 +417,7 @@ "symbols": 1853 }, { - "build_ms": 211, + "build_ms": 272, "ecosystem": "npm", "package": "react-dom@18.3.1", "sections": 7, @@ -428,13 +428,13 @@ } }, "react19": { - "ms": 514, + "ms": 662, "report": { "missing": [], - "ms": 506, + "ms": 651, "packages": [ { - "build_ms": 96, + "build_ms": 145, "ecosystem": "npm", "package": "@types/react@19.2.18", "sections": 5, @@ -442,7 +442,7 @@ "symbols": 1829 }, { - "build_ms": 9, + "build_ms": 15, "ecosystem": "npm", "package": "@types/react-dom@19.2.7", "sections": 5, @@ -450,7 +450,7 @@ "symbols": 206 }, { - "build_ms": 278, + "build_ms": 216, "ecosystem": "npm", "package": "react@19.2.8", "sections": 4, @@ -458,7 +458,7 @@ "symbols": 1829 }, { - "build_ms": 451, + "build_ms": 589, "ecosystem": "npm", "package": "react-dom@19.2.8", "sections": 7, @@ -469,13 +469,13 @@ } }, "rr6": { - "ms": 204, + "ms": 290, "report": { "missing": [], - "ms": 198, + "ms": 282, "packages": [ { - "build_ms": 31, + "build_ms": 47, "ecosystem": "npm", "package": "react@18.3.1", "sections": 4, @@ -483,7 +483,7 @@ "symbols": 0 }, { - "build_ms": 149, + "build_ms": 216, "ecosystem": "npm", "package": "react-dom@18.3.1", "sections": 7, @@ -491,7 +491,7 @@ "symbols": 0 }, { - "build_ms": 16, + "build_ms": 24, "ecosystem": "npm", "package": "react-router@6.26.2", "sections": 74, @@ -499,7 +499,7 @@ "symbols": 207 }, { - "build_ms": 15, + "build_ms": 218, "ecosystem": "npm", "package": "react-router-dom@6.26.2", "sections": 80, @@ -510,13 +510,13 @@ } }, "rr7": { - "ms": 399, + "ms": 515, "report": { "missing": [], - "ms": 393, + "ms": 507, "packages": [ { - "build_ms": 32, + "build_ms": 258, "ecosystem": "npm", "package": "react@19.0.0", "sections": 4, @@ -524,7 +524,7 @@ "symbols": 0 }, { - "build_ms": 346, + "build_ms": 450, "ecosystem": "npm", "package": "react-dom@19.0.0", "sections": 7, @@ -532,7 +532,7 @@ "symbols": 0 }, { - "build_ms": 227, + "build_ms": 296, "ecosystem": "npm", "package": "react-router@7.1.1", "sections": 92, @@ -543,13 +543,13 @@ } }, "sqlalchemy14": { - "ms": 354, + "ms": 500, "report": { "missing": [], - "ms": 338, + "ms": 483, "packages": [ { - "build_ms": 291, + "build_ms": 417, "ecosystem": "pypi", "package": "sqlalchemy@1.4.54", "sections": 8, @@ -560,13 +560,13 @@ } }, "sqlalchemy20": { - "ms": 460, + "ms": 650, "report": { "missing": [], - "ms": 440, + "ms": 631, "packages": [ { - "build_ms": 389, + "build_ms": 568, "ecosystem": "pypi", "package": "sqlalchemy@2.0.54", "sections": 8, @@ -577,13 +577,13 @@ } }, "tailwind3": { - "ms": 38, + "ms": 46, "report": { "missing": [], - "ms": 35, + "ms": 42, "packages": [ { - "build_ms": 6, + "build_ms": 9, "ecosystem": "npm", "package": "tailwindcss@3.4.19", "sections": 6, @@ -594,13 +594,13 @@ } }, "tailwind4": { - "ms": 35, + "ms": 44, "report": { "missing": [], - "ms": 33, + "ms": 40, "packages": [ { - "build_ms": 4, + "build_ms": 7, "ecosystem": "npm", "package": "tailwindcss@4.1.18", "sections": 4, @@ -611,13 +611,13 @@ } }, "vite5": { - "ms": 72, + "ms": 93, "report": { "missing": [], - "ms": 68, + "ms": 88, "packages": [ { - "build_ms": 38, + "build_ms": 51, "ecosystem": "npm", "package": "vite@5.4.21", "sections": 131, @@ -628,13 +628,13 @@ } }, "vite6": { - "ms": 78, + "ms": 100, "report": { "missing": [], - "ms": 73, + "ms": 94, "packages": [ { - "build_ms": 42, + "build_ms": 55, "ecosystem": "npm", "package": "vite@6.4.3", "sections": 91, @@ -645,13 +645,13 @@ } }, "zod3": { - "ms": 57, + "ms": 73, "report": { "missing": [], - "ms": 54, + "ms": 69, "packages": [ { - "build_ms": 24, + "build_ms": 31, "ecosystem": "npm", "package": "zod@3.23.8", "sections": 121, @@ -662,13 +662,13 @@ } }, "zod4": { - "ms": 84, + "ms": 115, "report": { "missing": [], - "ms": 79, + "ms": 109, "packages": [ { - "build_ms": 47, + "build_ms": 68, "ecosystem": "npm", "package": "zod@4.1.5", "sections": 9, @@ -681,10 +681,10 @@ }, "fetch": { "axum07": { - "ms": 2051, + "ms": 2469, "report": { "model": true, - "ms": 2048, + "ms": 2465, "packages": [ { "files": 2, @@ -704,10 +704,10 @@ } }, "axum08": { - "ms": 2992, + "ms": 2192, "report": { "model": true, - "ms": 2988, + "ms": 2187, "packages": [ { "files": 0, @@ -727,10 +727,10 @@ } }, "django42": { - "ms": 3471, + "ms": 4160, "report": { "model": true, - "ms": 3468, + "ms": 4156, "packages": [ { "files": 601, @@ -743,10 +743,10 @@ } }, "django51": { - "ms": 3698, + "ms": 4787, "report": { "model": true, - "ms": 3694, + "ms": 4783, "packages": [ { "files": 629, @@ -759,10 +759,10 @@ } }, "eslint8": { - "ms": 1530, + "ms": 3476, "report": { "model": true, - "ms": 1527, + "ms": 3471, "packages": [ { "files": 409, @@ -775,10 +775,10 @@ } }, "eslint9": { - "ms": 5287, + "ms": 3330, "report": { "model": true, - "ms": 5284, + "ms": 3325, "packages": [ { "files": 435, @@ -791,10 +791,10 @@ } }, "express4": { - "ms": 3302, + "ms": 3582, "report": { "model": true, - "ms": 3299, + "ms": 3578, "packages": [ { "files": 6, @@ -814,10 +814,10 @@ } }, "express5": { - "ms": 2617, + "ms": 2913, "report": { "model": true, - "ms": 2614, + "ms": 2909, "packages": [ { "files": 38, @@ -837,10 +837,10 @@ } }, "fastapi0115": { - "ms": 2637, + "ms": 2264, "report": { "model": true, - "ms": 2633, + "ms": 2260, "packages": [ { "files": 180, @@ -853,10 +853,10 @@ } }, "fastapi088": { - "ms": 1892, + "ms": 2594, "report": { "model": true, - "ms": 1888, + "ms": 2590, "packages": [ { "files": 113, @@ -869,10 +869,10 @@ } }, "next14": { - "ms": 1903, + "ms": 3699, "report": { "model": true, - "ms": 1899, + "ms": 3694, "packages": [ { "files": 318, @@ -899,10 +899,10 @@ } }, "next15": { - "ms": 2112, + "ms": 4137, "report": { "model": true, - "ms": 2108, + "ms": 4131, "packages": [ { "files": 365, @@ -929,10 +929,10 @@ } }, "next16": { - "ms": 2083, + "ms": 3730, "report": { "model": true, - "ms": 2078, + "ms": 3724, "packages": [ { "files": 458, @@ -959,10 +959,10 @@ } }, "prisma5": { - "ms": 5175, + "ms": 6014, "report": { "model": true, - "ms": 5170, + "ms": 6009, "packages": [ { "files": 236, @@ -982,10 +982,10 @@ } }, "prisma6": { - "ms": 3127, + "ms": 3632, "report": { "model": true, - "ms": 3123, + "ms": 3626, "packages": [ { "files": 247, @@ -1005,10 +1005,10 @@ } }, "pydantic1": { - "ms": 810, + "ms": 1757, "report": { "model": true, - "ms": 808, + "ms": 1754, "packages": [ { "files": 176, @@ -1021,10 +1021,10 @@ } }, "pydantic2": { - "ms": 668, + "ms": 1177, "report": { "model": true, - "ms": 665, + "ms": 1173, "packages": [ { "files": 80, @@ -1037,10 +1037,10 @@ } }, "react18": { - "ms": 2670, + "ms": 2923, "report": { "model": true, - "ms": 2667, + "ms": 2919, "packages": [ { "files": 137, @@ -1074,10 +1074,10 @@ } }, "react19": { - "ms": 3657, + "ms": 2912, "report": { "model": true, - "ms": 3654, + "ms": 2908, "packages": [ { "files": 180, @@ -1111,10 +1111,10 @@ } }, "rr6": { - "ms": 1667, + "ms": 2148, "report": { "model": true, - "ms": 1664, + "ms": 2144, "packages": [ { "files": 140, @@ -1148,10 +1148,10 @@ } }, "rr7": { - "ms": 1544, + "ms": 2033, "report": { "model": true, - "ms": 1541, + "ms": 2029, "packages": [ { "files": 184, @@ -1178,10 +1178,10 @@ } }, "sqlalchemy14": { - "ms": 2206, + "ms": 2995, "report": { "model": true, - "ms": 2203, + "ms": 2991, "packages": [ { "files": 179, @@ -1194,10 +1194,10 @@ } }, "sqlalchemy20": { - "ms": 2165, + "ms": 3057, "report": { "model": true, - "ms": 2162, + "ms": 3053, "packages": [ { "files": 198, @@ -1210,10 +1210,10 @@ } }, "tailwind3": { - "ms": 1582, + "ms": 3464, "report": { "model": true, - "ms": 1579, + "ms": 3460, "packages": [ { "files": 192, @@ -1226,10 +1226,10 @@ } }, "tailwind4": { - "ms": 1981, + "ms": 7799, "report": { "model": true, - "ms": 1977, + "ms": 7795, "packages": [ { "files": 224, @@ -1242,10 +1242,10 @@ } }, "vite5": { - "ms": 1001, + "ms": 1136, "report": { "model": true, - "ms": 999, + "ms": 1133, "packages": [ { "files": 37, @@ -1258,10 +1258,10 @@ } }, "vite6": { - "ms": 923, + "ms": 1367, "report": { "model": true, - "ms": 920, + "ms": 1364, "packages": [ { "files": 47, @@ -1274,10 +1274,10 @@ } }, "zod3": { - "ms": 644, + "ms": 630, "report": { "model": true, - "ms": 642, + "ms": 628, "packages": [ { "files": 4, @@ -1290,10 +1290,10 @@ } }, "zod4": { - "ms": 1134, + "ms": 1337, "report": { "model": true, - "ms": 1131, + "ms": 1334, "packages": [ { "files": 18, @@ -1321,8 +1321,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 1072, - "ms": 68.9 + "tokens": 1075, + "ms": 86.7 }, "variants": { "keyword": { @@ -1330,21 +1330,21 @@ "missing": [], "rejected": [], "tokens": 1091, - "ms": 23.8 + "ms": 32.0 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 1091, - "ms": 33.4 + "ms": 41.9 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 1072, - "ms": 68.9 + "tokens": 1075, + "ms": 86.7 } }, "context7": { @@ -1373,7 +1373,7 @@ "missing": [], "rejected": [], "tokens": 1015, - "ms": 107.7 + "ms": 141.6 }, "variants": { "keyword": { @@ -1381,7 +1381,7 @@ "missing": [], "rejected": [], "tokens": 1103, - "ms": 43.7 + "ms": 61.9 }, "hybrid": { "pass": false, @@ -1392,14 +1392,14 @@ ], "rejected": [], "tokens": 1144, - "ms": 39.4 + "ms": 57.0 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 1015, - "ms": 107.7 + "ms": 141.6 } }, "context7": { @@ -1427,8 +1427,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 1122, - "ms": 35.7 + "tokens": 1119, + "ms": 47.1 }, "variants": { "keyword": { @@ -1436,21 +1436,21 @@ "missing": [], "rejected": [], "tokens": 1018, - "ms": 5.2 + "ms": 6.7 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 1019, - "ms": 32.9 + "ms": 40.2 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 1122, - "ms": 35.7 + "tokens": 1119, + "ms": 47.1 } }, "context7": { @@ -1478,8 +1478,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 998, - "ms": 46.2 + "tokens": 903, + "ms": 60.2 }, "variants": { "keyword": { @@ -1487,21 +1487,21 @@ "missing": [], "rejected": [], "tokens": 616, - "ms": 10.0 + "ms": 13.2 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 389, - "ms": 41.5 + "ms": 56.9 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 998, - "ms": 46.2 + "tokens": 903, + "ms": 60.2 } }, "context7": { @@ -1529,8 +1529,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 995, - "ms": 36.6 + "tokens": 991, + "ms": 47.3 }, "variants": { "keyword": { @@ -1538,21 +1538,21 @@ "missing": [], "rejected": [], "tokens": 979, - "ms": 5.0 + "ms": 6.4 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 920, - "ms": 33.3 + "ms": 46.9 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 995, - "ms": 36.6 + "tokens": 991, + "ms": 47.3 } }, "context7": { @@ -1581,7 +1581,7 @@ "missing": [], "rejected": [], "tokens": 991, - "ms": 44.6 + "ms": 59.9 }, "variants": { "keyword": { @@ -1589,21 +1589,21 @@ "missing": [], "rejected": [], "tokens": 392, - "ms": 9.7 + "ms": 12.6 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 608, - "ms": 39.0 + "ms": 55.5 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 991, - "ms": 44.6 + "ms": 59.9 } }, "context7": { @@ -1632,7 +1632,7 @@ "missing": [], "rejected": [], "tokens": 946, - "ms": 45.9 + "ms": 55.2 }, "variants": { "keyword": { @@ -1644,7 +1644,7 @@ ], "rejected": [], "tokens": 1117, - "ms": 10.0 + "ms": 13.5 }, "hybrid": { "pass": false, @@ -1655,14 +1655,14 @@ ], "rejected": [], "tokens": 1077, - "ms": 40.6 + "ms": 52.4 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 946, - "ms": 45.9 + "ms": 55.2 } }, "context7": { @@ -1695,7 +1695,7 @@ ], "rejected": [], "tokens": 926, - "ms": 290.4 + "ms": 394.7 }, "variants": { "keyword": { @@ -1707,14 +1707,14 @@ ], "rejected": [], "tokens": 302, - "ms": 99.4 + "ms": 134.7 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 507, - "ms": 51.4 + "ms": 68.1 }, "fetched": { "pass": false, @@ -1725,7 +1725,7 @@ ], "rejected": [], "tokens": 926, - "ms": 290.4 + "ms": 394.7 } }, "context7": { @@ -1757,8 +1757,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 864, - "ms": 369.3 + "tokens": 866, + "ms": 448.2 }, "variants": { "keyword": { @@ -1766,21 +1766,21 @@ "missing": [], "rejected": [], "tokens": 737, - "ms": 118.5 + "ms": 155.7 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 693, - "ms": 54.2 + "ms": 66.1 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 864, - "ms": 369.3 + "tokens": 866, + "ms": 448.2 } }, "context7": { @@ -1809,7 +1809,7 @@ "missing": [], "rejected": [], "tokens": 896, - "ms": 83.8 + "ms": 93.6 }, "variants": { "keyword": { @@ -1817,21 +1817,21 @@ "missing": [], "rejected": [], "tokens": 360, - "ms": 19.1 + "ms": 23.8 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 783, - "ms": 50.0 + "ms": 62.9 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 896, - "ms": 83.8 + "ms": 93.6 } }, "context7": { @@ -1860,7 +1860,7 @@ "missing": [], "rejected": [], "tokens": 870, - "ms": 91.2 + "ms": 105.9 }, "variants": { "keyword": { @@ -1868,21 +1868,21 @@ "missing": [], "rejected": [], "tokens": 351, - "ms": 22.1 + "ms": 26.5 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 734, - "ms": 54.7 + "ms": 70.1 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 870, - "ms": 91.2 + "ms": 105.9 } }, "context7": { @@ -1911,7 +1911,7 @@ "missing": [], "rejected": [], "tokens": 843, - "ms": 77.1 + "ms": 96.9 }, "variants": { "keyword": { @@ -1919,21 +1919,21 @@ "missing": [], "rejected": [], "tokens": 908, - "ms": 18.9 + "ms": 23.6 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 893, - "ms": 52.7 + "ms": 61.0 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 843, - "ms": 77.1 + "ms": 96.9 } }, "context7": { @@ -1962,7 +1962,7 @@ "missing": [], "rejected": [], "tokens": 754, - "ms": 83.5 + "ms": 108.3 }, "variants": { "keyword": { @@ -1970,21 +1970,21 @@ "missing": [], "rejected": [], "tokens": 934, - "ms": 20.2 + "ms": 26.8 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 964, - "ms": 56.0 + "ms": 70.7 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 754, - "ms": 83.5 + "ms": 108.3 } }, "context7": { @@ -2012,8 +2012,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 874, - "ms": 82.7 + "tokens": 827, + "ms": 99.7 }, "variants": { "keyword": { @@ -2021,21 +2021,21 @@ "missing": [], "rejected": [], "tokens": 923, - "ms": 21.4 + "ms": 25.9 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 932, - "ms": 54.5 + "ms": 73.6 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 874, - "ms": 82.7 + "tokens": 827, + "ms": 99.7 } }, "context7": { @@ -2068,7 +2068,7 @@ ], "rejected": [], "tokens": 796, - "ms": 90.9 + "ms": 120.4 }, "variants": { "keyword": { @@ -2076,14 +2076,14 @@ "missing": [], "rejected": [], "tokens": 913, - "ms": 8.5 + "ms": 11.0 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 891, - "ms": 32.3 + "ms": 37.7 }, "fetched": { "pass": false, @@ -2094,7 +2094,7 @@ ], "rejected": [], "tokens": 796, - "ms": 90.9 + "ms": 120.4 } }, "context7": { @@ -2126,8 +2126,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 828, - "ms": 107.2 + "tokens": 876, + "ms": 143.5 }, "variants": { "keyword": { @@ -2135,7 +2135,7 @@ "missing": [], "rejected": [], "tokens": 909, - "ms": 34.1 + "ms": 47.8 }, "hybrid": { "pass": false, @@ -2146,14 +2146,14 @@ ], "rejected": [], "tokens": 970, - "ms": 36.6 + "ms": 43.6 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 828, - "ms": 107.2 + "tokens": 876, + "ms": 143.5 } }, "context7": { @@ -2182,7 +2182,7 @@ "missing": [], "rejected": [], "tokens": 872, - "ms": 41.9 + "ms": 50.1 }, "variants": { "keyword": { @@ -2190,21 +2190,21 @@ "missing": [], "rejected": [], "tokens": 821, - "ms": 3.2 + "ms": 4.0 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 884, - "ms": 31.6 + "ms": 41.8 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 872, - "ms": 41.9 + "ms": 50.1 } }, "context7": { @@ -2233,7 +2233,7 @@ "missing": [], "rejected": [], "tokens": 755, - "ms": 41.4 + "ms": 55.9 }, "variants": { "keyword": { @@ -2241,21 +2241,21 @@ "missing": [], "rejected": [], "tokens": 946, - "ms": 3.1 + "ms": 4.0 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 904, - "ms": 31.9 + "ms": 41.7 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 755, - "ms": 41.4 + "ms": 55.9 } }, "context7": { @@ -2284,7 +2284,7 @@ "missing": [], "rejected": [], "tokens": 807, - "ms": 45.3 + "ms": 58.5 }, "variants": { "keyword": { @@ -2292,21 +2292,21 @@ "missing": [], "rejected": [], "tokens": 918, - "ms": 7.2 + "ms": 9.7 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 931, - "ms": 36.8 + "ms": 43.6 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 807, - "ms": 45.3 + "ms": 58.5 } }, "context7": { @@ -2334,8 +2334,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 839, - "ms": 118.2 + "tokens": 846, + "ms": 157.0 }, "variants": { "keyword": { @@ -2343,21 +2343,21 @@ "missing": [], "rejected": [], "tokens": 915, - "ms": 48.0 + "ms": 67.1 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 915, - "ms": 45.4 + "ms": 59.9 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 839, - "ms": 118.2 + "tokens": 846, + "ms": 157.0 } }, "context7": { @@ -2392,8 +2392,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 957, - "ms": 212.8 + "tokens": 952, + "ms": 278.7 }, "variants": { "keyword": { @@ -2405,21 +2405,21 @@ ], "rejected": [], "tokens": 924, - "ms": 99.5 + "ms": 141.6 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 843, - "ms": 49.2 + "ms": 64.2 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 957, - "ms": 212.8 + "tokens": 952, + "ms": 278.7 } }, "context7": { @@ -2444,15 +2444,11 @@ "set": "tuning", "grading": "[[[\"parse_obj\"]], [\"model_validate\"]]", "lockdocs": { - "pass": false, - "missing": [ - [ - "parse_obj" - ] - ], + "pass": true, + "missing": [], "rejected": [], - "tokens": 875, - "ms": 54.8 + "tokens": 845, + "ms": 63.7 }, "variants": { "keyword": { @@ -2464,7 +2460,7 @@ ], "rejected": [], "tokens": 900, - "ms": 15.7 + "ms": 21.1 }, "hybrid": { "pass": false, @@ -2475,18 +2471,14 @@ ], "rejected": [], "tokens": 922, - "ms": 44.7 + "ms": 59.2 }, "fetched": { - "pass": false, - "missing": [ - [ - "parse_obj" - ] - ], + "pass": true, + "missing": [], "rejected": [], - "tokens": 875, - "ms": 54.8 + "tokens": 845, + "ms": 63.7 } }, "context7": { @@ -2520,8 +2512,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 829, - "ms": 68.8 + "tokens": 825, + "ms": 76.0 }, "variants": { "keyword": { @@ -2533,21 +2525,21 @@ ], "rejected": [], "tokens": 941, - "ms": 20.1 + "ms": 25.6 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 858, - "ms": 50.7 + "ms": 59.6 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 829, - "ms": 68.8 + "tokens": 825, + "ms": 76.0 } }, "context7": { @@ -2575,8 +2567,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 965, - "ms": 53.1 + "tokens": 961, + "ms": 62.4 }, "variants": { "keyword": { @@ -2584,21 +2576,21 @@ "missing": [], "rejected": [], "tokens": 908, - "ms": 15.5 + "ms": 20.4 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 867, - "ms": 45.9 + "ms": 58.3 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 965, - "ms": 53.1 + "tokens": 961, + "ms": 62.4 } }, "context7": { @@ -2633,8 +2625,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 777, - "ms": 65.5 + "tokens": 775, + "ms": 77.4 }, "variants": { "keyword": { @@ -2646,21 +2638,21 @@ ], "rejected": [], "tokens": 826, - "ms": 19.3 + "ms": 25.4 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 867, - "ms": 50.6 + "ms": 60.2 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 777, - "ms": 65.5 + "tokens": 775, + "ms": 77.4 } }, "context7": { @@ -2689,7 +2681,7 @@ "missing": [], "rejected": [], "tokens": 818, - "ms": 52.9 + "ms": 68.0 }, "variants": { "keyword": { @@ -2697,21 +2689,21 @@ "missing": [], "rejected": [], "tokens": 854, - "ms": 15.3 + "ms": 19.8 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 915, - "ms": 45.2 + "ms": 53.8 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 818, - "ms": 52.9 + "ms": 68.0 } }, "context7": { @@ -2747,8 +2739,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 824, - "ms": 67.0 + "tokens": 880, + "ms": 78.9 }, "variants": { "keyword": { @@ -2756,21 +2748,21 @@ "missing": [], "rejected": [], "tokens": 839, - "ms": 19.8 + "ms": 26.0 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 884, - "ms": 49.5 + "ms": 65.0 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 824, - "ms": 67.0 + "tokens": 880, + "ms": 78.9 } }, "context7": { @@ -2803,8 +2795,8 @@ ] ], "rejected": [], - "tokens": 924, - "ms": 53.6 + "tokens": 923, + "ms": 64.1 }, "variants": { "keyword": { @@ -2817,7 +2809,7 @@ ], "rejected": [], "tokens": 878, - "ms": 15.5 + "ms": 20.5 }, "hybrid": { "pass": false, @@ -2829,7 +2821,7 @@ ], "rejected": [], "tokens": 945, - "ms": 48.1 + "ms": 55.1 }, "fetched": { "pass": false, @@ -2840,8 +2832,8 @@ ] ], "rejected": [], - "tokens": 924, - "ms": 53.6 + "tokens": 923, + "ms": 64.1 } }, "context7": { @@ -2853,7 +2845,8 @@ "tokens": 683, "ms": 2725, "library": "/pydantic/pydantic", - "version_match": "unversioned" + "version_match": "unversioned", + "reused": true } }, { @@ -2870,8 +2863,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 956, - "ms": 65.5 + "tokens": 952, + "ms": 80.1 }, "variants": { "keyword": { @@ -2879,21 +2872,21 @@ "missing": [], "rejected": [], "tokens": 898, - "ms": 19.5 + "ms": 25.5 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 892, - "ms": 49.5 + "ms": 64.2 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 956, - "ms": 65.5 + "tokens": 952, + "ms": 80.1 } }, "context7": { @@ -2921,8 +2914,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 1018, - "ms": 73.0 + "tokens": 1020, + "ms": 102.6 }, "variants": { "keyword": { @@ -2930,21 +2923,21 @@ "missing": [], "rejected": [], "tokens": 1003, - "ms": 41.9 + "ms": 58.0 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 1005, - "ms": 35.0 + "ms": 47.1 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 1018, - "ms": 73.0 + "tokens": 1020, + "ms": 102.6 } }, "context7": { @@ -2976,30 +2969,30 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 935, - "ms": 76.1 + "tokens": 962, + "ms": 96.9 }, "variants": { "keyword": { "pass": true, "missing": [], "rejected": [], - "tokens": 955, - "ms": 43.8 + "tokens": 960, + "ms": 60.2 }, "hybrid": { "pass": true, "missing": [], "rejected": [], - "tokens": 935, - "ms": 36.4 + "tokens": 962, + "ms": 48.8 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 935, - "ms": 76.1 + "tokens": 962, + "ms": 96.9 } }, "context7": { @@ -3028,7 +3021,7 @@ "missing": [], "rejected": [], "tokens": 912, - "ms": 34.5 + "ms": 45.8 }, "variants": { "keyword": { @@ -3036,21 +3029,21 @@ "missing": [], "rejected": [], "tokens": 907, - "ms": 6.7 + "ms": 8.7 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 911, - "ms": 35.7 + "ms": 41.0 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 912, - "ms": 34.5 + "ms": 45.8 } }, "context7": { @@ -3083,7 +3076,7 @@ "missing": [], "rejected": [], "tokens": 891, - "ms": 35.4 + "ms": 41.4 }, "variants": { "keyword": { @@ -3091,21 +3084,21 @@ "missing": [], "rejected": [], "tokens": 683, - "ms": 6.3 + "ms": 8.8 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 891, - "ms": 36.2 + "ms": 43.6 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 891, - "ms": 35.4 + "ms": 41.4 } }, "context7": { @@ -3134,7 +3127,7 @@ "missing": [], "rejected": [], "tokens": 823, - "ms": 260.7 + "ms": 362.6 }, "variants": { "keyword": { @@ -3142,21 +3135,21 @@ "missing": [], "rejected": [], "tokens": 796, - "ms": 194.4 + "ms": 276.2 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 796, - "ms": 51.3 + "ms": 67.8 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 823, - "ms": 260.7 + "ms": 362.6 } }, "context7": { @@ -3185,7 +3178,7 @@ "missing": [], "rejected": [], "tokens": 962, - "ms": 53.2 + "ms": 68.8 }, "variants": { "keyword": { @@ -3197,7 +3190,7 @@ ], "rejected": [], "tokens": 956, - "ms": 22.8 + "ms": 28.2 }, "hybrid": { "pass": false, @@ -3208,14 +3201,14 @@ ], "rejected": [], "tokens": 970, - "ms": 52.7 + "ms": 60.6 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 962, - "ms": 53.2 + "ms": 68.8 } }, "context7": { @@ -3244,7 +3237,7 @@ "missing": [], "rejected": [], "tokens": 941, - "ms": 52.5 + "ms": 62.7 }, "variants": { "keyword": { @@ -3252,21 +3245,21 @@ "missing": [], "rejected": [], "tokens": 849, - "ms": 21.2 + "ms": 27.7 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 903, - "ms": 57.9 + "ms": 61.6 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 941, - "ms": 52.5 + "ms": 62.7 } }, "context7": { @@ -3295,7 +3288,7 @@ "missing": [], "rejected": [], "tokens": 971, - "ms": 130.0 + "ms": 169.9 }, "variants": { "keyword": { @@ -3309,7 +3302,7 @@ ], "rejected": [], "tokens": 1134, - "ms": 8.6 + "ms": 11.5 }, "hybrid": { "pass": false, @@ -3322,14 +3315,14 @@ ], "rejected": [], "tokens": 1025, - "ms": 32.6 + "ms": 36.6 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 971, - "ms": 130.0 + "ms": 169.9 } }, "context7": { @@ -3366,7 +3359,7 @@ "missing": [], "rejected": [], "tokens": 979, - "ms": 144.3 + "ms": 192.6 }, "variants": { "keyword": { @@ -3379,7 +3372,7 @@ ], "rejected": [], "tokens": 358, - "ms": 6.6 + "ms": 9.4 }, "hybrid": { "pass": false, @@ -3391,14 +3384,14 @@ ], "rejected": [], "tokens": 461, - "ms": 31.7 + "ms": 39.3 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 979, - "ms": 144.3 + "ms": 192.6 } }, "context7": { @@ -3427,7 +3420,7 @@ "missing": [], "rejected": [], "tokens": 1003, - "ms": 48.5 + "ms": 63.2 }, "variants": { "keyword": { @@ -3435,21 +3428,21 @@ "missing": [], "rejected": [], "tokens": 1003, - "ms": 2.9 + "ms": 3.5 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 1047, - "ms": 32.3 + "ms": 40.9 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 1003, - "ms": 48.5 + "ms": 63.2 } }, "context7": { @@ -3478,7 +3471,7 @@ "missing": [], "rejected": [], "tokens": 1144, - "ms": 49.3 + "ms": 63.9 }, "variants": { "keyword": { @@ -3491,7 +3484,7 @@ ], "rejected": [], "tokens": 286, - "ms": 1.6 + "ms": 2.1 }, "hybrid": { "pass": false, @@ -3503,14 +3496,14 @@ ], "rejected": [], "tokens": 764, - "ms": 31.7 + "ms": 39.3 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 1144, - "ms": 49.3 + "ms": 63.9 } }, "context7": { @@ -3538,8 +3531,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 797, - "ms": 345.2 + "tokens": 796, + "ms": 473.9 }, "variants": { "keyword": { @@ -3547,21 +3540,21 @@ "missing": [], "rejected": [], "tokens": 912, - "ms": 99.8 + "ms": 150.4 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 925, - "ms": 40.1 + "ms": 44.5 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 797, - "ms": 345.2 + "tokens": 796, + "ms": 473.9 } }, "context7": { @@ -3590,7 +3583,7 @@ "missing": [], "rejected": [], "tokens": 720, - "ms": 308.9 + "ms": 416.7 }, "variants": { "keyword": { @@ -3598,21 +3591,21 @@ "missing": [], "rejected": [], "tokens": 882, - "ms": 26.3 + "ms": 34.7 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 961, - "ms": 36.7 + "ms": 40.9 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 720, - "ms": 308.9 + "ms": 416.7 } }, "context7": { @@ -3641,7 +3634,7 @@ "missing": [], "rejected": [], "tokens": 854, - "ms": 74.2 + "ms": 88.1 }, "variants": { "keyword": { @@ -3654,7 +3647,7 @@ ], "rejected": [], "tokens": 871, - "ms": 8.0 + "ms": 10.4 }, "hybrid": { "pass": false, @@ -3666,14 +3659,14 @@ ], "rejected": [], "tokens": 898, - "ms": 37.0 + "ms": 50.7 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 854, - "ms": 74.2 + "ms": 88.1 } }, "context7": { @@ -3702,7 +3695,7 @@ "missing": [], "rejected": [], "tokens": 849, - "ms": 77.3 + "ms": 98.9 }, "variants": { "keyword": { @@ -3716,7 +3709,7 @@ ], "rejected": [], "tokens": 919, - "ms": 6.0 + "ms": 7.9 }, "hybrid": { "pass": false, @@ -3729,14 +3722,14 @@ ], "rejected": [], "tokens": 898, - "ms": 34.6 + "ms": 52.8 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 849, - "ms": 77.3 + "ms": 98.9 } }, "context7": { @@ -3768,8 +3761,8 @@ ] ], "rejected": [], - "tokens": 829, - "ms": 296.4 + "tokens": 846, + "ms": 384.9 }, "variants": { "keyword": { @@ -3781,7 +3774,7 @@ ], "rejected": [], "tokens": 945, - "ms": 26.6 + "ms": 34.7 }, "hybrid": { "pass": false, @@ -3792,7 +3785,7 @@ ], "rejected": [], "tokens": 963, - "ms": 34.0 + "ms": 45.5 }, "fetched": { "pass": false, @@ -3802,8 +3795,8 @@ ] ], "rejected": [], - "tokens": 829, - "ms": 296.4 + "tokens": 846, + "ms": 384.9 } }, "context7": { @@ -3837,8 +3830,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 854, - "ms": 330.5 + "tokens": 848, + "ms": 437.5 }, "variants": { "keyword": { @@ -3846,21 +3839,21 @@ "missing": [], "rejected": [], "tokens": 925, - "ms": 37.9 + "ms": 52.8 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 910, - "ms": 33.9 + "ms": 41.2 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 854, - "ms": 330.5 + "tokens": 848, + "ms": 437.5 } }, "context7": { @@ -3889,7 +3882,7 @@ "missing": [], "rejected": [], "tokens": 849, - "ms": 291.0 + "ms": 384.6 }, "variants": { "keyword": { @@ -3901,7 +3894,7 @@ ], "rejected": [], "tokens": 583, - "ms": 27.0 + "ms": 35.3 }, "hybrid": { "pass": false, @@ -3912,14 +3905,14 @@ ], "rejected": [], "tokens": 948, - "ms": 35.0 + "ms": 40.3 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 849, - "ms": 291.0 + "ms": 384.6 } }, "context7": { @@ -3949,8 +3942,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 820, - "ms": 353.4 + "tokens": 846, + "ms": 471.2 }, "variants": { "keyword": { @@ -3962,7 +3955,7 @@ ], "rejected": [], "tokens": 583, - "ms": 37.5 + "ms": 52.8 }, "hybrid": { "pass": false, @@ -3973,14 +3966,14 @@ ], "rejected": [], "tokens": 942, - "ms": 33.8 + "ms": 44.5 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 820, - "ms": 353.4 + "tokens": 846, + "ms": 471.2 } }, "context7": { @@ -4016,7 +4009,7 @@ "useActionState" ], "tokens": 832, - "ms": 342.5 + "ms": 409.3 }, "variants": { "keyword": { @@ -4024,7 +4017,7 @@ "missing": [], "rejected": [], "tokens": 863, - "ms": 46.9 + "ms": 66.2 }, "hybrid": { "pass": false, @@ -4033,7 +4026,7 @@ "useActionState" ], "tokens": 959, - "ms": 37.6 + "ms": 49.0 }, "fetched": { "pass": false, @@ -4047,7 +4040,7 @@ "useActionState" ], "tokens": 832, - "ms": 342.5 + "ms": 409.3 } }, "context7": { @@ -4083,7 +4076,7 @@ "missing": [], "rejected": [], "tokens": 872, - "ms": 350.4 + "ms": 464.2 }, "variants": { "keyword": { @@ -4095,21 +4088,21 @@ ], "rejected": [], "tokens": 981, - "ms": 47.0 + "ms": 63.3 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 961, - "ms": 38.7 + "ms": 43.5 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 872, - "ms": 350.4 + "ms": 464.2 } }, "context7": { @@ -4138,7 +4131,7 @@ "missing": [], "rejected": [], "tokens": 823, - "ms": 76.7 + "ms": 98.7 }, "variants": { "keyword": { @@ -4151,7 +4144,7 @@ ], "rejected": [], "tokens": 403, - "ms": 7.1 + "ms": 9.0 }, "hybrid": { "pass": false, @@ -4163,14 +4156,14 @@ ], "rejected": [], "tokens": 925, - "ms": 37.2 + "ms": 44.0 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 823, - "ms": 76.7 + "ms": 98.7 } }, "context7": { @@ -4201,7 +4194,7 @@ "missing": [], "rejected": [], "tokens": 854, - "ms": 83.8 + "ms": 104.4 }, "variants": { "keyword": { @@ -4214,7 +4207,7 @@ ], "rejected": [], "tokens": 923, - "ms": 6.8 + "ms": 8.7 }, "hybrid": { "pass": false, @@ -4226,14 +4219,14 @@ ], "rejected": [], "tokens": 941, - "ms": 37.8 + "ms": 45.6 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 854, - "ms": 83.8 + "ms": 104.4 } }, "context7": { @@ -4261,8 +4254,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 887, - "ms": 139.6 + "tokens": 897, + "ms": 185.9 }, "variants": { "keyword": { @@ -4270,21 +4263,21 @@ "missing": [], "rejected": [], "tokens": 880, - "ms": 40.0 + "ms": 50.2 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 966, - "ms": 37.3 + "ms": 45.3 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 887, - "ms": 139.6 + "tokens": 897, + "ms": 185.9 } }, "context7": { @@ -4315,7 +4308,7 @@ "missing": [], "rejected": [], "tokens": 839, - "ms": 121.8 + "ms": 164.9 }, "variants": { "keyword": { @@ -4323,21 +4316,21 @@ "missing": [], "rejected": [], "tokens": 797, - "ms": 42.3 + "ms": 55.4 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 899, - "ms": 39.3 + "ms": 51.8 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 839, - "ms": 121.8 + "ms": 164.9 } }, "context7": { @@ -4366,7 +4359,7 @@ "missing": [], "rejected": [], "tokens": 813, - "ms": 74.3 + "ms": 95.9 }, "variants": { "keyword": { @@ -4381,7 +4374,7 @@ ], "rejected": [], "tokens": 900, - "ms": 17.2 + "ms": 22.9 }, "hybrid": { "pass": false, @@ -4395,14 +4388,14 @@ ], "rejected": [], "tokens": 163, - "ms": 32.2 + "ms": 43.9 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 813, - "ms": 74.3 + "ms": 95.9 } }, "context7": { @@ -4433,7 +4426,7 @@ "missing": [], "rejected": [], "tokens": 932, - "ms": 71.2 + "ms": 97.0 }, "variants": { "keyword": { @@ -4448,7 +4441,7 @@ ], "rejected": [], "tokens": 473, - "ms": 6.6 + "ms": 8.8 }, "hybrid": { "pass": false, @@ -4462,14 +4455,14 @@ ], "rejected": [], "tokens": 968, - "ms": 29.8 + "ms": 42.0 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 932, - "ms": 71.2 + "ms": 97.0 } }, "context7": { @@ -4497,8 +4490,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 871, - "ms": 712.2 + "tokens": 852, + "ms": 977.9 }, "variants": { "keyword": { @@ -4506,21 +4499,21 @@ "missing": [], "rejected": [], "tokens": 847, - "ms": 309.7 + "ms": 431.6 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 860, - "ms": 77.3 + "ms": 92.0 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 871, - "ms": 712.2 + "tokens": 852, + "ms": 977.9 } }, "context7": { @@ -4550,8 +4543,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 768, - "ms": 851.1 + "tokens": 815, + "ms": 1167.4 }, "variants": { "keyword": { @@ -4564,21 +4557,21 @@ ], "rejected": [], "tokens": 810, - "ms": 407.0 + "ms": 575.8 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 872, - "ms": 86.4 + "ms": 104.2 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 768, - "ms": 851.1 + "tokens": 815, + "ms": 1167.4 } }, "context7": { @@ -4606,8 +4599,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 834, - "ms": 142.9 + "tokens": 828, + "ms": 166.4 }, "variants": { "keyword": { @@ -4615,21 +4608,21 @@ "missing": [], "rejected": [], "tokens": 917, - "ms": 43.1 + "ms": 54.0 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 931, - "ms": 78.7 + "ms": 95.2 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 834, - "ms": 142.9 + "tokens": 828, + "ms": 166.4 } }, "context7": { @@ -4659,8 +4652,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 871, - "ms": 157.4 + "tokens": 863, + "ms": 189.8 }, "variants": { "keyword": { @@ -4668,21 +4661,21 @@ "missing": [], "rejected": [], "tokens": 831, - "ms": 55.6 + "ms": 67.4 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 897, - "ms": 87.4 + "ms": 103.4 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 871, - "ms": 157.4 + "tokens": 863, + "ms": 189.8 } }, "context7": { @@ -4710,8 +4703,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 815, - "ms": 889.8 + "tokens": 812, + "ms": 1185.4 }, "variants": { "keyword": { @@ -4723,7 +4716,7 @@ ], "rejected": [], "tokens": 770, - "ms": 335.2 + "ms": 444.6 }, "hybrid": { "pass": false, @@ -4734,14 +4727,14 @@ ], "rejected": [], "tokens": 839, - "ms": 84.2 + "ms": 101.1 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 815, - "ms": 889.8 + "tokens": 812, + "ms": 1185.4 } }, "context7": { @@ -4769,8 +4762,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 818, - "ms": 909.2 + "tokens": 802, + "ms": 1214.2 }, "variants": { "keyword": { @@ -4782,21 +4775,21 @@ ], "rejected": [], "tokens": 860, - "ms": 326.6 + "ms": 473.4 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 777, - "ms": 84.6 + "ms": 104.4 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 818, - "ms": 909.2 + "tokens": 802, + "ms": 1214.2 } }, "context7": { @@ -4824,8 +4817,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 794, - "ms": 170.9 + "tokens": 790, + "ms": 205.6 }, "variants": { "keyword": { @@ -4833,21 +4826,21 @@ "missing": [], "rejected": [], "tokens": 862, - "ms": 47.7 + "ms": 57.8 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 853, - "ms": 83.0 + "ms": 101.9 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 794, - "ms": 170.9 + "tokens": 790, + "ms": 205.6 } }, "context7": { @@ -4875,8 +4868,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 740, - "ms": 173.3 + "tokens": 736, + "ms": 207.1 }, "variants": { "keyword": { @@ -4884,21 +4877,21 @@ "missing": [], "rejected": [], "tokens": 520, - "ms": 48.6 + "ms": 58.4 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 845, - "ms": 86.7 + "ms": 97.9 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 740, - "ms": 173.3 + "tokens": 736, + "ms": 207.1 } }, "context7": { @@ -4930,8 +4923,8 @@ ] ], "rejected": [], - "tokens": 878, - "ms": 156.1 + "tokens": 873, + "ms": 203.4 }, "variants": { "keyword": { @@ -4943,7 +4936,7 @@ ], "rejected": [], "tokens": 372, - "ms": 26.0 + "ms": 34.4 }, "hybrid": { "pass": false, @@ -4954,7 +4947,7 @@ ], "rejected": [], "tokens": 555, - "ms": 40.5 + "ms": 47.4 }, "fetched": { "pass": false, @@ -4964,8 +4957,8 @@ ] ], "rejected": [], - "tokens": 878, - "ms": 156.1 + "tokens": 873, + "ms": 203.4 } }, "context7": { @@ -4999,8 +4992,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 832, - "ms": 199.0 + "tokens": 828, + "ms": 256.8 }, "variants": { "keyword": { @@ -5012,7 +5005,7 @@ ], "rejected": [], "tokens": 729, - "ms": 37.1 + "ms": 49.9 }, "hybrid": { "pass": false, @@ -5023,14 +5016,14 @@ ], "rejected": [], "tokens": 898, - "ms": 41.5 + "ms": 55.8 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 832, - "ms": 199.0 + "tokens": 828, + "ms": 256.8 } }, "context7": { @@ -5058,8 +5051,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 875, - "ms": 58.2 + "tokens": 865, + "ms": 75.7 }, "variants": { "keyword": { @@ -5071,7 +5064,7 @@ ], "rejected": [], "tokens": 803, - "ms": 12.5 + "ms": 15.4 }, "hybrid": { "pass": false, @@ -5082,14 +5075,14 @@ ], "rejected": [], "tokens": 985, - "ms": 41.3 + "ms": 52.4 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 875, - "ms": 58.2 + "tokens": 865, + "ms": 75.7 } }, "context7": { @@ -5119,8 +5112,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 783, - "ms": 63.9 + "tokens": 766, + "ms": 81.7 }, "variants": { "keyword": { @@ -5128,21 +5121,21 @@ "missing": [], "rejected": [], "tokens": 933, - "ms": 12.8 + "ms": 15.7 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 930, - "ms": 41.1 + "ms": 51.1 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 783, - "ms": 63.9 + "tokens": 766, + "ms": 81.7 } }, "context7": { @@ -5171,7 +5164,7 @@ "missing": [], "rejected": [], "tokens": 797, - "ms": 81.8 + "ms": 103.5 }, "variants": { "keyword": { @@ -5179,21 +5172,21 @@ "missing": [], "rejected": [], "tokens": 897, - "ms": 21.3 + "ms": 26.4 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 946, - "ms": 56.9 + "ms": 63.8 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 797, - "ms": 81.8 + "ms": 103.5 } }, "context7": { @@ -5224,7 +5217,7 @@ "missing": [], "rejected": [], "tokens": 816, - "ms": 532.3 + "ms": 718.2 }, "variants": { "keyword": { @@ -5232,21 +5225,21 @@ "missing": [], "rejected": [], "tokens": 847, - "ms": 396.5 + "ms": 520.0 }, "hybrid": { "pass": true, "missing": [], "rejected": [], - "tokens": 828, - "ms": 117.0 + "tokens": 865, + "ms": 142.9 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 816, - "ms": 532.3 + "ms": 718.2 } }, "context7": { @@ -5275,29 +5268,29 @@ "missing": [], "rejected": [], "tokens": 881, - "ms": 78.0 + "ms": 96.9 }, "variants": { "keyword": { "pass": true, "missing": [], "rejected": [], - "tokens": 928, - "ms": 20.7 + "tokens": 874, + "ms": 24.6 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 917, - "ms": 50.9 + "ms": 66.1 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 881, - "ms": 78.0 + "ms": 96.9 } }, "context7": { @@ -5322,16 +5315,11 @@ "set": "tuning", "grading": "[[[\"await params\", \"Promise<{\"]], []]", "lockdocs": { - "pass": false, - "missing": [ - [ - "await params", - "Promise<{" - ] - ], + "pass": true, + "missing": [], "rejected": [], - "tokens": 881, - "ms": 84.9 + "tokens": 880, + "ms": 104.9 }, "variants": { "keyword": { @@ -5343,8 +5331,8 @@ ] ], "rejected": [], - "tokens": 949, - "ms": 20.8 + "tokens": 927, + "ms": 27.2 }, "hybrid": { "pass": false, @@ -5356,19 +5344,14 @@ ], "rejected": [], "tokens": 946, - "ms": 55.2 + "ms": 69.7 }, "fetched": { - "pass": false, - "missing": [ - [ - "await params", - "Promise<{" - ] - ], + "pass": true, + "missing": [], "rejected": [], - "tokens": 881, - "ms": 84.9 + "tokens": 880, + "ms": 104.9 } }, "context7": { @@ -5397,7 +5380,7 @@ "missing": [], "rejected": [], "tokens": 896, - "ms": 50.0 + "ms": 65.7 }, "variants": { "keyword": { @@ -5405,27 +5388,31 @@ "missing": [], "rejected": [], "tokens": 420, - "ms": 2.8 + "ms": 3.5 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 420, - "ms": 32.0 + "ms": 43.6 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 896, - "ms": 50.0 + "ms": 65.7 } }, "context7": { - "error": "HTTP 404", - "ms": 3327.2388550000187, - "library": "/rails/tailwindcss-rails" + "pass": true, + "missing": [], + "rejected": [], + "tokens": 578, + "ms": 6783, + "library": "/erimicel/select2-tailwindcss-theme", + "version_match": "unversioned" } }, { @@ -5443,7 +5430,7 @@ "missing": [], "rejected": [], "tokens": 874, - "ms": 49.5 + "ms": 67.8 }, "variants": { "keyword": { @@ -5455,7 +5442,7 @@ ], "rejected": [], "tokens": 297, - "ms": 1.7 + "ms": 2.1 }, "hybrid": { "pass": false, @@ -5466,20 +5453,28 @@ ], "rejected": [], "tokens": 516, - "ms": 29.8 + "ms": 39.3 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 874, - "ms": 49.5 + "ms": 67.8 } }, "context7": { - "error": "HTTP 404", - "ms": 3210.879936000026, - "library": "/rails/tailwindcss-rails" + "pass": false, + "missing": [ + [ + "@custom-variant" + ] + ], + "rejected": [], + "tokens": 578, + "ms": 6704, + "library": "/erimicel/select2-tailwindcss-theme", + "version_match": "unversioned" } }, { @@ -5497,7 +5492,7 @@ "missing": [], "rejected": [], "tokens": 899, - "ms": 76.7 + "ms": 101.2 }, "variants": { "keyword": { @@ -5505,21 +5500,21 @@ "missing": [], "rejected": [], "tokens": 856, - "ms": 7.0 + "ms": 8.8 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 907, - "ms": 36.5 + "ms": 43.9 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 899, - "ms": 76.7 + "ms": 101.2 } }, "context7": { @@ -5548,7 +5543,7 @@ "missing": [], "rejected": [], "tokens": 796, - "ms": 84.3 + "ms": 111.4 }, "variants": { "keyword": { @@ -5561,7 +5556,7 @@ ], "rejected": [], "tokens": 925, - "ms": 6.9 + "ms": 8.7 }, "hybrid": { "pass": false, @@ -5573,14 +5568,14 @@ ], "rejected": [], "tokens": 816, - "ms": 36.2 + "ms": 46.4 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 796, - "ms": 84.3 + "ms": 111.4 } }, "context7": { @@ -5609,7 +5604,7 @@ "missing": [], "rejected": [], "tokens": 790, - "ms": 42.0 + "ms": 57.1 }, "variants": { "keyword": { @@ -5617,21 +5612,21 @@ "missing": [], "rejected": [], "tokens": 792, - "ms": 2.9 + "ms": 3.9 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 881, - "ms": 32.9 + "ms": 37.9 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 790, - "ms": 42.0 + "ms": 57.1 } }, "context7": { @@ -5663,7 +5658,7 @@ "missing": [], "rejected": [], "tokens": 851, - "ms": 44.3 + "ms": 58.4 }, "variants": { "keyword": { @@ -5676,7 +5671,7 @@ ], "rejected": [], "tokens": 918, - "ms": 7.0 + "ms": 9.2 }, "hybrid": { "pass": false, @@ -5688,14 +5683,14 @@ ], "rejected": [], "tokens": 868, - "ms": 37.3 + "ms": 48.1 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 851, - "ms": 44.3 + "ms": 58.4 } }, "context7": { @@ -5729,7 +5724,7 @@ ], "rejected": [], "tokens": 768, - "ms": 74.1 + "ms": 95.2 }, "variants": { "keyword": { @@ -5742,7 +5737,7 @@ ], "rejected": [], "tokens": 866, - "ms": 7.3 + "ms": 9.9 }, "hybrid": { "pass": false, @@ -5754,7 +5749,7 @@ ], "rejected": [], "tokens": 843, - "ms": 39.3 + "ms": 46.1 }, "fetched": { "pass": false, @@ -5766,7 +5761,7 @@ ], "rejected": [], "tokens": 768, - "ms": 74.1 + "ms": 95.2 } }, "context7": { @@ -5795,7 +5790,7 @@ "missing": [], "rejected": [], "tokens": 741, - "ms": 79.7 + "ms": 96.4 }, "variants": { "keyword": { @@ -5807,7 +5802,7 @@ ], "rejected": [], "tokens": 1027, - "ms": 5.6 + "ms": 7.8 }, "hybrid": { "pass": false, @@ -5818,14 +5813,14 @@ ], "rejected": [], "tokens": 1017, - "ms": 37.8 + "ms": 40.8 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 741, - "ms": 79.7 + "ms": 96.4 } }, "context7": { @@ -5853,8 +5848,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 770, - "ms": 176.8 + "tokens": 750, + "ms": 217.7 }, "variants": { "keyword": { @@ -5866,7 +5861,7 @@ ], "rejected": [], "tokens": 798, - "ms": 48.2 + "ms": 60.3 }, "hybrid": { "pass": false, @@ -5877,14 +5872,14 @@ ], "rejected": [], "tokens": 803, - "ms": 87.2 + "ms": 99.0 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 770, - "ms": 176.8 + "tokens": 750, + "ms": 217.7 } }, "context7": { @@ -5912,8 +5907,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 901, - "ms": 62.3 + "tokens": 887, + "ms": 84.7 }, "variants": { "keyword": { @@ -5926,8 +5921,8 @@ ] ], "rejected": [], - "tokens": 891, - "ms": 13.0 + "tokens": 896, + "ms": 16.6 }, "hybrid": { "pass": false, @@ -5939,15 +5934,15 @@ ] ], "rejected": [], - "tokens": 908, - "ms": 42.8 + "tokens": 877, + "ms": 56.1 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 901, - "ms": 62.3 + "tokens": 887, + "ms": 84.7 } }, "context7": { @@ -5976,7 +5971,7 @@ "missing": [], "rejected": [], "tokens": 881, - "ms": 53.1 + "ms": 70.7 }, "variants": { "keyword": { @@ -5984,21 +5979,21 @@ "missing": [], "rejected": [], "tokens": 851, - "ms": 22.2 + "ms": 27.6 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 842, - "ms": 52.5 + "ms": 64.8 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 881, - "ms": 53.1 + "ms": 70.7 } }, "context7": { @@ -6026,8 +6021,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 1165, - "ms": 35.0 + "tokens": 1162, + "ms": 44.0 }, "variants": { "keyword": { @@ -6035,21 +6030,21 @@ "missing": [], "rejected": [], "tokens": 985, - "ms": 5.2 + "ms": 6.4 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 854, - "ms": 34.7 + "ms": 45.0 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 1165, - "ms": 35.0 + "tokens": 1162, + "ms": 44.0 } }, "context7": { @@ -6080,7 +6075,7 @@ "missing": [], "rejected": [], "tokens": 1158, - "ms": 43.3 + "ms": 61.1 }, "variants": { "keyword": { @@ -6088,21 +6083,21 @@ "missing": [], "rejected": [], "tokens": 510, - "ms": 10.4 + "ms": 13.2 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 386, - "ms": 43.1 + "ms": 56.6 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 1158, - "ms": 43.3 + "ms": 61.1 } }, "context7": { @@ -6130,8 +6125,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 924, - "ms": 49.8 + "tokens": 930, + "ms": 69.0 }, "variants": { "keyword": { @@ -6139,7 +6134,7 @@ "missing": [], "rejected": [], "tokens": 972, - "ms": 15.1 + "ms": 19.9 }, "hybrid": { "pass": false, @@ -6150,14 +6145,14 @@ ], "rejected": [], "tokens": 949, - "ms": 45.7 + "ms": 58.3 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 924, - "ms": 49.8 + "tokens": 930, + "ms": 69.0 } }, "context7": { @@ -6169,7 +6164,8 @@ "tokens": 1092, "ms": 1857, "library": "/pydantic/pydantic", - "version_match": "unversioned" + "version_match": "unversioned", + "reused": true } }, { @@ -6186,8 +6182,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 888, - "ms": 60.7 + "tokens": 884, + "ms": 81.9 }, "variants": { "keyword": { @@ -6199,7 +6195,7 @@ ], "rejected": [], "tokens": 829, - "ms": 18.7 + "ms": 25.3 }, "hybrid": { "pass": false, @@ -6210,14 +6206,14 @@ ], "rejected": [], "tokens": 917, - "ms": 50.9 + "ms": 59.1 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 888, - "ms": 60.7 + "tokens": 884, + "ms": 81.9 } }, "context7": { @@ -6246,7 +6242,7 @@ "missing": [], "rejected": [], "tokens": 898, - "ms": 82.2 + "ms": 103.3 }, "variants": { "keyword": { @@ -6258,7 +6254,7 @@ ], "rejected": [], "tokens": 895, - "ms": 21.4 + "ms": 27.4 }, "hybrid": { "pass": false, @@ -6269,14 +6265,14 @@ ], "rejected": [], "tokens": 881, - "ms": 57.6 + "ms": 71.0 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 898, - "ms": 82.2 + "ms": 103.3 } }, "context7": { @@ -6286,7 +6282,8 @@ "tokens": 950, "ms": 3170, "library": "/vercel/next.js/v15.1.11", - "version_match": "same major" + "version_match": "same major", + "reused": true } }, { @@ -6304,7 +6301,7 @@ "missing": [], "rejected": [], "tokens": 856, - "ms": 117.5 + "ms": 142.4 }, "variants": { "keyword": { @@ -6312,21 +6309,21 @@ "missing": [], "rejected": [], "tokens": 881, - "ms": 72.9 + "ms": 92.0 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 834, - "ms": 123.0 + "ms": 147.5 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 856, - "ms": 117.5 + "ms": 142.4 } }, "context7": { @@ -6336,7 +6333,8 @@ "tokens": 1258, "ms": 3159, "library": "/vercel/next.js/v16.2.9", - "version_match": "same major" + "version_match": "same major", + "reused": true } }, { @@ -6353,8 +6351,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 979, - "ms": 35.0 + "tokens": 975, + "ms": 45.2 }, "variants": { "keyword": { @@ -6362,21 +6360,21 @@ "missing": [], "rejected": [], "tokens": 858, - "ms": 5.2 + "ms": 6.5 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 957, - "ms": 35.0 + "ms": 40.1 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 979, - "ms": 35.0 + "tokens": 975, + "ms": 45.2 } }, "context7": { @@ -6386,7 +6384,8 @@ "tokens": 132, "ms": 2243, "library": "/colinhacks/zod/v3.24.2", - "version_match": "same major" + "version_match": "same major", + "reused": true } }, { @@ -6404,7 +6403,7 @@ "missing": [], "rejected": [], "tokens": 888, - "ms": 42.5 + "ms": 55.0 }, "variants": { "keyword": { @@ -6416,7 +6415,7 @@ ], "rejected": [], "tokens": 196, - "ms": 10.0 + "ms": 12.1 }, "hybrid": { "pass": false, @@ -6427,14 +6426,14 @@ ], "rejected": [], "tokens": 1139, - "ms": 43.3 + "ms": 48.8 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 888, - "ms": 42.5 + "ms": 55.0 } }, "context7": { @@ -6444,7 +6443,8 @@ "tokens": 1048, "ms": 3495, "library": "/colinhacks/zod/v4.0.1", - "version_match": "same major" + "version_match": "same major", + "reused": true } }, { @@ -6461,8 +6461,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 896, - "ms": 59.2 + "tokens": 892, + "ms": 81.5 }, "variants": { "keyword": { @@ -6470,21 +6470,21 @@ "missing": [], "rejected": [], "tokens": 901, - "ms": 19.8 + "ms": 25.6 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 901, - "ms": 49.3 + "ms": 63.9 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 896, - "ms": 59.2 + "tokens": 892, + "ms": 81.5 } }, "context7": { @@ -6494,7 +6494,8 @@ "tokens": 1135, "ms": 2870, "library": "/pydantic/pydantic", - "version_match": "unversioned" + "version_match": "unversioned", + "reused": true } }, { @@ -6511,8 +6512,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 962, - "ms": 50.0 + "tokens": 938, + "ms": 69.8 }, "variants": { "keyword": { @@ -6524,7 +6525,7 @@ ], "rejected": [], "tokens": 909, - "ms": 15.8 + "ms": 19.7 }, "hybrid": { "pass": false, @@ -6535,14 +6536,14 @@ ], "rejected": [], "tokens": 882, - "ms": 44.5 + "ms": 66.5 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 962, - "ms": 50.0 + "tokens": 938, + "ms": 69.8 } }, "context7": { @@ -6558,7 +6559,8 @@ "tokens": 1319, "ms": 2189, "library": "/pydantic/pydantic", - "version_match": "unversioned" + "version_match": "unversioned", + "reused": true } }, { @@ -6575,8 +6577,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 876, - "ms": 61.4 + "tokens": 872, + "ms": 79.0 }, "variants": { "keyword": { @@ -6584,21 +6586,21 @@ "missing": [], "rejected": [], "tokens": 861, - "ms": 19.7 + "ms": 25.1 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 908, - "ms": 50.3 + "ms": 64.0 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 876, - "ms": 61.4 + "tokens": 872, + "ms": 79.0 } }, "context7": { @@ -6608,7 +6610,8 @@ "tokens": 1319, "ms": 1630, "library": "/pydantic/pydantic", - "version_match": "unversioned" + "version_match": "unversioned", + "reused": true } }, { @@ -6625,8 +6628,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 858, - "ms": 156.4 + "tokens": 867, + "ms": 192.9 }, "variants": { "keyword": { @@ -6634,21 +6637,21 @@ "missing": [], "rejected": [], "tokens": 1017, - "ms": 49.6 + "ms": 62.5 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 1017, - "ms": 89.0 + "ms": 102.7 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 858, - "ms": 156.4 + "tokens": 867, + "ms": 192.9 } }, "context7": { @@ -6658,7 +6661,8 @@ "tokens": 976, "ms": 2516, "library": "/websites/sqlalchemy_en_20", - "version_match": "unversioned" + "version_match": "unversioned", + "reused": true } }, { @@ -6676,7 +6680,7 @@ "missing": [], "rejected": [], "tokens": 784, - "ms": 72.4 + "ms": 90.0 }, "variants": { "keyword": { @@ -6689,7 +6693,7 @@ ], "rejected": [], "tokens": 895, - "ms": 8.3 + "ms": 10.3 }, "hybrid": { "pass": false, @@ -6701,14 +6705,14 @@ ], "rejected": [], "tokens": 905, - "ms": 38.4 + "ms": 47.9 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 784, - "ms": 72.4 + "ms": 90.0 } }, "context7": { @@ -6718,7 +6722,8 @@ "tokens": 990, "ms": 2698, "library": "/eslint/eslint/v8.57.1", - "version_match": "exact" + "version_match": "exact", + "reused": true } }, { @@ -6740,7 +6745,7 @@ ], "rejected": [], "tokens": 788, - "ms": 76.0 + "ms": 101.2 }, "variants": { "keyword": { @@ -6752,7 +6757,7 @@ ], "rejected": [], "tokens": 943, - "ms": 6.3 + "ms": 7.8 }, "hybrid": { "pass": false, @@ -6763,7 +6768,7 @@ ], "rejected": [], "tokens": 943, - "ms": 36.3 + "ms": 48.0 }, "fetched": { "pass": false, @@ -6774,7 +6779,7 @@ ], "rejected": [], "tokens": 788, - "ms": 76.0 + "ms": 101.2 } }, "context7": { @@ -6784,7 +6789,8 @@ "tokens": 959, "ms": 2626, "library": "/eslint/eslint/v9.39.3", - "version_match": "same major" + "version_match": "same major", + "reused": true } }, { @@ -6801,8 +6807,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 676, - "ms": 45.3 + "tokens": 673, + "ms": 62.4 }, "variants": { "keyword": { @@ -6810,7 +6816,7 @@ "missing": [], "rejected": [], "tokens": 917, - "ms": 8.0 + "ms": 10.5 }, "hybrid": { "pass": false, @@ -6821,14 +6827,14 @@ ], "rejected": [], "tokens": 851, - "ms": 41.3 + "ms": 51.5 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 676, - "ms": 45.3 + "tokens": 673, + "ms": 62.4 } }, "context7": { @@ -6838,7 +6844,8 @@ "tokens": 840, "ms": 2923, "library": "/vitejs/vite", - "version_match": "unversioned" + "version_match": "unversioned", + "reused": true } }, { @@ -6856,7 +6863,7 @@ "missing": [], "rejected": [], "tokens": 823, - "ms": 77.4 + "ms": 97.5 }, "variants": { "keyword": { @@ -6864,7 +6871,7 @@ "missing": [], "rejected": [], "tokens": 952, - "ms": 6.5 + "ms": 8.7 }, "hybrid": { "pass": false, @@ -6875,14 +6882,14 @@ ], "rejected": [], "tokens": 948, - "ms": 38.8 + "ms": 42.4 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 823, - "ms": 77.4 + "ms": 97.5 } }, "context7": { @@ -6894,7 +6901,8 @@ "tokens": 1155, "ms": 2796, "library": "/reactjs/react.dev", - "version_match": "unversioned" + "version_match": "unversioned", + "reused": true } }, { @@ -6912,7 +6920,7 @@ "missing": [], "rejected": [], "tokens": 812, - "ms": 82.1 + "ms": 104.1 }, "variants": { "keyword": { @@ -6925,7 +6933,7 @@ ], "rejected": [], "tokens": 902, - "ms": 6.4 + "ms": 8.9 }, "hybrid": { "pass": false, @@ -6937,14 +6945,14 @@ ], "rejected": [], "tokens": 903, - "ms": 38.4 + "ms": 51.1 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 812, - "ms": 82.1 + "ms": 104.1 } }, "context7": { @@ -6954,7 +6962,8 @@ "tokens": 778, "ms": 2187, "library": "/reactjs/react.dev", - "version_match": "unversioned" + "version_match": "unversioned", + "reused": true } }, { @@ -6972,7 +6981,7 @@ "missing": [], "rejected": [], "tokens": 898, - "ms": 43.4 + "ms": 55.4 }, "variants": { "keyword": { @@ -6984,7 +6993,7 @@ ], "rejected": [], "tokens": 883, - "ms": 6.5 + "ms": 9.2 }, "hybrid": { "pass": false, @@ -6995,14 +7004,14 @@ ], "rejected": [], "tokens": 923, - "ms": 36.9 + "ms": 49.5 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 898, - "ms": 43.4 + "ms": 55.4 } }, "context7": { @@ -7012,7 +7021,8 @@ "tokens": 583, "ms": 2735, "library": "/websites/reactrouter", - "version_match": "unversioned" + "version_match": "unversioned", + "reused": true } }, { @@ -7029,8 +7039,8 @@ "pass": true, "missing": [], "rejected": [], - "tokens": 837, - "ms": 174.1 + "tokens": 769, + "ms": 209.4 }, "variants": { "keyword": { @@ -7042,7 +7052,7 @@ ], "rejected": [], "tokens": 873, - "ms": 46.9 + "ms": 61.1 }, "hybrid": { "pass": false, @@ -7053,14 +7063,14 @@ ], "rejected": [], "tokens": 912, - "ms": 86.6 + "ms": 103.2 }, "fetched": { "pass": true, "missing": [], "rejected": [], - "tokens": 837, - "ms": 174.1 + "tokens": 769, + "ms": 209.4 } }, "context7": { @@ -7070,7 +7080,8 @@ "tokens": 440, "ms": 2828, "library": "/django/django", - "version_match": "unversioned" + "version_match": "unversioned", + "reused": true } }, { @@ -7088,7 +7099,7 @@ "missing": [], "rejected": [], "tokens": 860, - "ms": 48.4 + "ms": 61.7 }, "variants": { "keyword": { @@ -7100,7 +7111,7 @@ ], "rejected": [], "tokens": 1276, - "ms": 3.0 + "ms": 3.6 }, "hybrid": { "pass": false, @@ -7111,14 +7122,14 @@ ], "rejected": [], "tokens": 1209, - "ms": 33.0 + "ms": 44.1 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 860, - "ms": 48.4 + "ms": 61.7 } }, "context7": { @@ -7128,7 +7139,8 @@ "tokens": 511, "ms": 1488, "library": "/rails/tailwindcss-rails", - "version_match": "unversioned" + "version_match": "unversioned", + "reused": true } }, { @@ -7150,7 +7162,7 @@ ], "rejected": [], "tokens": 939, - "ms": 50.2 + "ms": 66.7 }, "variants": { "keyword": { @@ -7162,7 +7174,7 @@ ], "rejected": [], "tokens": 402, - "ms": 1.8 + "ms": 2.2 }, "hybrid": { "pass": false, @@ -7173,7 +7185,7 @@ ], "rejected": [], "tokens": 434, - "ms": 31.0 + "ms": 41.7 }, "fetched": { "pass": false, @@ -7184,7 +7196,7 @@ ], "rejected": [], "tokens": 939, - "ms": 50.2 + "ms": 66.7 } }, "context7": { @@ -7198,7 +7210,8 @@ "tokens": 511, "ms": 1274, "library": "/rails/tailwindcss-rails", - "version_match": "unversioned" + "version_match": "unversioned", + "reused": true } }, { @@ -7216,7 +7229,7 @@ "missing": [], "rejected": [], "tokens": 890, - "ms": 52.9 + "ms": 65.2 }, "variants": { "keyword": { @@ -7224,21 +7237,21 @@ "missing": [], "rejected": [], "tokens": 877, - "ms": 20.4 + "ms": 27.5 }, "hybrid": { "pass": true, "missing": [], "rejected": [], "tokens": 840, - "ms": 53.1 + "ms": 62.6 }, "fetched": { "pass": true, "missing": [], "rejected": [], "tokens": 890, - "ms": 52.9 + "ms": 65.2 } }, "context7": { @@ -7248,7 +7261,8 @@ "tokens": 133, "ms": 2762, "library": "/websites/rs_tokio_tokio", - "version_match": "unversioned" + "version_match": "unversioned", + "reused": true } } ], @@ -7257,33 +7271,33 @@ "answered": 105, "passed": 59, "total": 105, - "median_tokens": 883, - "median_ms": 19.1, - "p95_ms": 194.4 + "median_tokens": 882, + "median_ms": 24.6, + "p95_ms": 276.2 }, "hybrid": { "answered": 105, "passed": 60, "total": 105, - "median_tokens": 904, - "median_ms": 40.6, - "p95_ms": 86.7 + "median_tokens": 903, + "median_ms": 52.4, + "p95_ms": 103.2 }, "fetched": { "answered": 105, - "passed": 94, + "passed": 96, "total": 105, - "median_tokens": 871, - "median_ms": 76.1, - "p95_ms": 369.3 + "median_tokens": 866, + "median_ms": 96.9, + "p95_ms": 473.9 }, "context7": { - "answered": 103, - "passed": 76, + "answered": 105, + "passed": 77, "total": 105, - "median_tokens": 910, - "median_ms": 2575, - "p95_ms": 3320 + "median_tokens": 908, + "median_ms": 2583, + "p95_ms": 3398 } }, "variants": [ @@ -7301,13 +7315,13 @@ ] ], "context7_calls": { - "calls": 39, + "calls": 8, "rate_limited": 0, - "errors": 2, + "errors": 4, "stopped": false, "limit": "200", - "remaining": "161", - "reused": 83 + "remaining": "190", + "reused": 103 }, "runner": { "os": "Linux",