Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
d1aef91
feat: build-generate AI retrieval layer (knowledge graph + index)
eugenia-scandit Aug 5, 2026
65676e0
ci: add docs retrieval-quality evals (success@k / precision@k / MRR)
eugenia-scandit Aug 5, 2026
d948727
ci(retrieval-evals): add capped recall@k metric + threshold
eugenia-scandit Aug 5, 2026
1748eaf
fix(ai-layer): declare cheerio, fail loud on empty extraction, atomic…
eugenia-scandit Aug 5, 2026
972e6cb
fix(retrieval-evals): drop redundant recall (= precision here), add p…
eugenia-scandit Aug 5, 2026
84ae98c
feat: ingest curated frontmatter into the AI retrieval index
eugenia-scandit Aug 6, 2026
fa3bb32
fix: keep curated intents in dedicated fields, not the length-capped …
eugenia-scandit Aug 6, 2026
1be9fbe
feat: fold user_intents into searchable keywords (not_for excluded)
eugenia-scandit Aug 6, 2026
2a6edd5
feat: emit all products for multi-product pages; rename status to fro…
eugenia-scandit Aug 7, 2026
0110d02
fix(knowledge-extractor): address the review findings on the retrieva…
eugenia-scandit Sep 4, 2026
f81509e
fix(knowledge-extractor): repair the chunking regression the last com…
eugenia-scandit Sep 4, 2026
284aaca
fix(ci): give the build the git history last_verified is read from
eugenia-scandit Sep 4, 2026
df7e4e0
fix(knowledge-extractor): restore link URLs in lists; date pages by t…
eugenia-scandit Sep 4, 2026
3a261f0
fix(knowledge-extractor): one shared walker; fence-safe excerpt; real…
eugenia-scandit Sep 4, 2026
457c3a3
fix(knowledge-extractor): clip the other excerpt; follow imports all …
eugenia-scandit Sep 4, 2026
4b71a67
fix(knowledge-extractor): the assistant excerpt kept its content; rea…
eugenia-scandit Sep 4, 2026
c1ed3bc
fix(knowledge-extractor): label tabbed variants; stop publishing 404 …
eugenia-scandit Sep 4, 2026
14f99c3
fix(knowledge-extractor): scope tab panels by ownership; label stays …
eugenia-scandit Sep 4, 2026
b4b80d3
fix(knowledge-extractor): label drop on every clip path; heading cann…
eugenia-scandit Sep 4, 2026
f657f9e
fix(knowledge-extractor): state the chunk invariant once, not per bou…
eugenia-scandit Sep 4, 2026
6552e81
fix(knowledge-extractor): make the invariant a fixpoint, and CI-enforced
eugenia-scandit Sep 4, 2026
378b65e
fix(knowledge-extractor): assert the invariant where the artifact is …
eugenia-scandit Sep 7, 2026
205cf59
fix(knowledge-extractor): demote until the line is no longer an intro…
eugenia-scandit Sep 7, 2026
5d54997
Merge branch 'main' into feat/ai-retrieval-layer
eugenia-scandit Sep 7, 2026
7ed5493
fix(knowledge-extractor): index the version actually served, not "cur…
eugenia-scandit Sep 7, 2026
70cc354
fix(knowledge-extractor): word-boundary auth intent, and stop indexin…
eugenia-scandit Sep 8, 2026
a5d6cf3
fix(knowledge-extractor): apply the curation list, not a paraphrase o…
eugenia-scandit Sep 8, 2026
6882f34
fix(knowledge-extractor): finish the frozen-root fix, and agree with …
eugenia-scandit Sep 8, 2026
7277c44
fix(knowledge-extractor): match the way the llms walker matches
eugenia-scandit Sep 8, 2026
f1eceb8
fix(knowledge-extractor): stop testing the segment the llms walker ne…
eugenia-scandit Sep 8, 2026
a384fbb
fix(FeatureList): correct the contrast figures to the built css
eugenia-scandit Sep 8, 2026
b626f33
Merge branch 'main' into feat/ai-retrieval-layer
eugenia-scandit Sep 18, 2026
553c1e0
Fix eight extraction defects review found, and gate the eval on a bas…
eugenia-scandit Sep 18, 2026
0e09b70
Re-record the retrieval baseline against the fixed artifact
eugenia-scandit Sep 18, 2026
eb0e6bb
Test the prose, not the heading, and stop the gate failing open
eugenia-scandit Sep 18, 2026
7015c49
Re-record the baseline after the title-only page is skipped
eugenia-scandit Sep 18, 2026
6d008d6
Apply the header-rendered-title lesson to the other guard, and close …
eugenia-scandit Sep 18, 2026
be5c0a0
Count ties against the page, and reach the prose past a sub-heading
eugenia-scandit Sep 18, 2026
4a4a63b
Give the gold-set search one slot per page, not per chunk
eugenia-scandit Sep 18, 2026
c8308df
Announce the unavailable chip as unavailable
eugenia-scandit Sep 18, 2026
7cc8bbc
Keep the feature-availability matrix out of the knowledge index
eugenia-scandit Sep 18, 2026
77d5651
Merge branch 'main' into feat/ai-retrieval-layer
eugenia-scandit Sep 18, 2026
6076605
Rank the evals with BM25, and stop full-cloning on pull requests
eugenia-scandit Sep 21, 2026
83fdd1f
Make the evals read last_verified, so the full clone buys something
eugenia-scandit Sep 21, 2026
a2a1131
Make the freshness guard opt-in, and name the key the guard actually …
eugenia-scandit Sep 21, 2026
0c0c6c5
Close three silent-failure paths in the extractor's file selection
eugenia-scandit Sep 21, 2026
767cf7e
Canonicalise the product through products.json, and carry baseUrl int…
eugenia-scandit Sep 21, 2026
0f38508
Stop the drift guard depending on a postBuild race, and rank --auto b…
eugenia-scandit Sep 21, 2026
dd917f8
Take the ignore list as a plugin option - a naming fix, not a size one
eugenia-scandit Sep 21, 2026
502ea97
Word-bound the troubleshoot test, let the registry decide every product,
eugenia-scandit Sep 21, 2026
8a55edd
Stop hiding the framework column the component still builds a header for
eugenia-scandit Sep 21, 2026
74b5eec
Finish the checked-flag conversion, and make an alias collision fail …
eugenia-scandit Sep 22, 2026
b5c57d3
Stop a guard I added from being swallowed, and four more silent paths
eugenia-scandit Sep 22, 2026
06dfdd3
Merge main, and stop the link regex truncating an href at its first p…
eugenia-scandit Sep 23, 2026
59fbb07
Stop the plugin header asserting numbers that keep going stale
eugenia-scandit Sep 24, 2026
5899288
Report the artifact sizes on the success line, so the header's pointe…
eugenia-scandit Sep 24, 2026
2feeee1
Close the NaN gate one level up, and stop the regex comment overselli…
eugenia-scandit Sep 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/workflows/build-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,26 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
# Shallow on EVERY trigger, because this job never publishes what the
# full clone would improve.
#
# The knowledge-extractor sets each page's `last_verified` from that
# file's last commit date, and under a depth-1 clone every file
# resolves to the same commit, so the plugin emits "" rather than a
# constant that looks like data. It warns and carries on; nothing here
# asserts on last_verified, so this cannot turn the job red.
#
# An earlier version of this kept full history on push/schedule, on
# the theory that those runs produce the artifact a deploy reads. They
# do not: this job has no deploy step and no upload-artifact step, so
# the index it builds is discarded with the runner on every trigger,
# push included. No workflow in this repo publishes it.
#
# docs-retrieval-evals.yml is the one that does use the dates - it
# reports freshness coverage and fails on --require-freshness - and it
# keeps fetch-depth: 0 for that reason.
fetch-depth: 1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking, reuse: this job has no concurrency group, unlike docs-preview.yml and the new docs-retrieval-evals.yml, both of which got one during this review for the same reason (a full yarn build plus several test steps queuing on every push rather than the latest one winning). This workflow has no paths: filter, so every PR to the repo pays for that, not just this one - and this PR alone has roughly 30 commits that would have triggered it. Worth the same pattern, e.g. concurrency: { group: build-docs-${{ github.event.number || github.sha }}, cancel-in-progress: true }.


- name: Setup Node.js
uses: actions/setup-node@v4
Expand Down
16 changes: 16 additions & 0 deletions .github/workflows/docs-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,22 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
# Shallow is enough HERE, and only here. last_verified is the one
# thing that needs full history - the extractor reads each file's last
# commit date, and under a depth-1 clone every file resolves to the
# same commit - but the extractor now returns before any git call on a
# preview build, because it must not emit its ~22 MB of JSON into the
# gh-pages branch that pr-preview-action commits. A full clone would
# buy a freshness signal nobody computes. build-docs.yml makes the
# same call for the same reason and is shallow on EVERY trigger: it
# builds and verifies but never publishes the index, so the dates
# would be computed and discarded there too.
#
# docs-retrieval-evals.yml is the one workflow that keeps
# fetch-depth: 0, because it is the one that reads the dates - it
# reports freshness coverage and fails on --require-freshness.
fetch-depth: 1

- name: Setup Node.js
uses: actions/setup-node@v4
Expand Down
126 changes: 126 additions & 0 deletions .github/workflows/docs-retrieval-evals.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
name: Docs Retrieval Evals

# Measures retrieval quality of the build-generated knowledge index against a
# gold set of real queries (success@k / precision@k / MRR). Runs on PRs that
# touch docs, the extractor, or the gold set — a regression in descriptions or
# partials that hurts retrieval fails here. Also runs on push to main, so the
# deploy branch is checked too (an empty index from a cause outside the PR
# paths — e.g. a Docusaurus upgrade renaming article selectors — is caught here
# as well as by the plugin's own fail-loud floor check during the build).

on:
pull_request:
branches:
- main
paths:
- 'docs/**'
# And the frozen snapshots. While docusaurus.config.ts pins lastVersion to
# one of them - a beta cycle - that snapshot IS the corpus this indexes,
# so a PR editing it changes exactly what is measured here and would
# otherwise run neither eval until after it merged.
- 'versioned_docs/**'
- 'src/plugins/knowledge-extractor/**'
- 'scripts/retrieval-evals/**'
- '.github/workflows/docs-retrieval-evals.yml'
# The extractor reads RENDERED html, so a component decides what the
# indexed text IS - it already carries a special case for FeatureList's
# "Loading features..." SSR placeholder, and productKeys() reads
# src/data. Without these, a PR that changes what a component renders
# (this one changes FeatureList) never runs the evals, and the first
# signal is the push-to-main trigger after it has merged.
- 'src/components/**'
- 'src/data/**'
# And src/theme, which decides the MARKUP: MDXComponents.js maps Tabs /
# TabItem and every custom component rendered inside article .markdown -
# tabsToMd is built entirely around that markup - and DocItem is swizzled.
# Swapping the Tabs implementation, or wrapping a component in one more
# element, changes the indexed text on hundreds of pages.
- 'src/theme/**'
# These decide WHAT gets indexed at all: assistantIgnoreFiles and
# lastVersion live in the config, and versions.json decides which
# trees are frozen. Without them a PR that widens or narrows the
# curation list changes the corpus and skips both evals.
- 'docusaurus.config.ts'
- 'versions.json'
push:
branches:
- main

# Same pattern as docs-preview.yml, for the same reason: every run here is a
# full yarn build plus two eval passes, and this workflow watches paths a docs
# PR touches on nearly every push. Without a group they queue and all run; with
# it, a new push supersedes the one still running. Keyed on the PR number so
# runs on main are never cancelled by a PR.
concurrency:
group: docs-retrieval-evals-${{ github.event.number || github.sha }}
cancel-in-progress: true

jobs:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking, reuse question: docs-preview.yml already has concurrency: { group: docs-preview-${{ github.event.number }}, cancel-in-progress: true } for exactly this situation. This workflow's pull_request trigger has no concurrency group, and each run does a full yarn build plus two eval passes. This PR alone has ~30 commits touching paths this workflow watches, so without a concurrency group those are ~30 overlapping full builds queued rather than superseded. Worth the same pattern here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in the 77d5651 merge commit — docs-retrieval-evals.yml now carries the same concurrency group as docs-preview.yml.

Closing this thread.

retrieval-evals:
name: Docs Retrieval Evals
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
# Full history, and unlike build-docs.yml this job USES it.
#
# The knowledge-extractor takes each page's `last_verified` from its
# last commit date. Under the default depth-1 clone every file
# resolves to the same single commit, so the plugin deliberately
# emits "" rather than a constant that looks like data.
#
# run.cjs reports freshness coverage from that field - how many
# modules carry a date, the oldest and newest, and how many are over
# 180 days old - and fails in CI if NOT ONE module has one, which is
# what losing this line would look like. So the cost buys a signal
# that is actually read, and the check guards the setting that
# produces it. Do not drop to depth 1 here without deleting that
# report first.
#
# Deliberately NOT a ranking input: last_verified is a date, and the
# scorer tokenises on [a-z0-9]{2,}, so it would enter the term stream
# as "2026"/"09"/"18" and match any query mentioning those numbers.
fetch-depth: 0

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'yarn'

- name: Install dependencies
run: yarn install --frozen-lockfile

- name: Build docs (generates the knowledge retrieval index)
id: build
run: yarn build

- name: Run retrieval evals
# --require-freshness: this job is the one that checks out full history
# on purpose, so a dateless index here means the checkout lost its
# fetch-depth. Other callers (build-docs.yml is shallow on PRs) get the
# report without the failure.
run: node scripts/retrieval-evals/run.cjs --report reports/retrieval-evals.json --require-freshness

# The gold-set run above measures 20 curated queries. This one asks every
# module to retrieve itself, which is the only check that covers pages
# nobody wrote a query for - and until now MIN_AUTO_SUCCESS gated nothing
# because no workflow passed --auto. Reported separately so a corpus-wide
# regression is attributable.
- name: Run corpus-wide self-retrieval
# Not always(): a gold-set breach above must not hide the corpus-wide
# number, but a FAILED BUILD must not add a second red step blaming a
# missing index instead - and always() fires on cancellation too.
if: ${{ !cancelled() && steps.build.outcome == 'success' }}
run: node scripts/retrieval-evals/run.cjs --auto --report reports/retrieval-evals-auto.json

- name: Upload eval report
if: always()
uses: actions/upload-artifact@v4
with:
name: retrieval-evals-report
path: |
reports/retrieval-evals.json
reports/retrieval-evals-auto.json
if-no-files-found: ignore
125 changes: 120 additions & 5 deletions docusaurus.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import remarkHideComments from "./src/plugins/remark-hide-comments";
import remarkOffloadPreviewMedia from "./src/plugins/remark-offload-preview-media";
import stripPreviewMediaPlugin from "./src/plugins/plugin-strip-preview-media";
import { UNRELEASED_FRAMEWORK_SLUGS } from "./src/constants/unreleasedFrameworks";
import knowledgeExtractor from "./src/plugins/knowledge-extractor";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole file now conflicts against current origin/main, confirmed with git merge-tree --write-tree (the legacy 3-arg git merge-tree used in earlier rounds under-reports this and reads clean, which is worth knowing for next time). PR #436 (robots.txt, merged 2026-09-24) added a robotsTxtPlugin function and wired it into the same plugins array and nearby ignore-list region this PR edits (assistantIgnoreFiles/llmsIgnoreFiles/knowledgeIndexIgnoreFiles around line 105-160, and the [knowledgeExtractor, ...] registration around line 871). The two additions don't overlap logically, each owns its own consts and its own plugin entry, so this should resolve to keeping both blocks — but git can't do that automatically given how close the insertion points are. Worth rebasing before a third file joins the conflict list.

dotenv.config(); // Load environment variables from .env file

const productionUrl = "https://docs.scandit.com";
Expand Down Expand Up @@ -95,19 +96,67 @@ const llmsRootRedirectOnlyDocs: string[] = [
"docs/system-requirements.mdx",
];

// Paths are matched by docusaurus-plugin-llms relative to siteDir (e.g. docs/...).
const llmsIgnoreFiles: string[] = [
/**
* CURATION: pages no AI consumer should be given at all - deprecated platforms,
* features a platform does not have, redirect-only stubs with no prose, and
* partials that are not routes. Shared by both AI exports, so a decision about
* what an assistant may see is made once.
*/
const assistantIgnoreFiles: string[] = [
"docs/connector-guides/**",
// Partials are imported into real pages, not standalone doc routes; omit so llms.txt URLs work.
"docs/partials/**",
...llmsRootRedirectOnlyDocs,
...llmsIgnoredSdkTrees,
...llmsLinuxPartialIgnore,
...llmsNonWebSdkRoots.flatMap((root) =>
llmsSharedPartialPageNames.map((name) => `docs/${root}/${name}`),
),
];

/**
* CORPUS SHAPE, for the llms export only: the same prose repeated under every
* non-Web SDK root, deduped to Web so a flat text corpus does not carry nine
* copies of it.
*
* Deliberately NOT shared with the knowledge-extractor, and this is the one
* place the two exports differ on purpose. That index exists to route a reader
* to the page they are on: someone reading Flutter docs needs the Flutter URL,
* not the Web one that happens to hold the canonical copy of the text. Applying
* this list there removed about 1,400 modules and with them the ability to
* answer "where in the Flutter docs does this live".
*/
const llmsDedupedToWeb: string[] = llmsNonWebSdkRoots.flatMap((root) =>
llmsSharedPartialPageNames.map((name) => `docs/${root}/${name}`),
);

/**
* CORPUS SHAPE, for the knowledge index only: the feature-availability matrix,
* every copy of it.
*
* The counterpart of llmsDedupedToWeb - each export states its own corpus shape
* while both share the curation decision in assistantIgnoreFiles. The llms
* export keeps ONE copy of this page, because a flat text corpus can carry a
* table; the index keeps none.
*
* A token-overlap consumer cannot use it and is actively harmed by it. The
* matrices are 120 modules over 10 URLs at 23-33 distinct tokens against an
* index median of 50, and they name every product - so they score well on ANY
* single-product query while answering none of them. Measured: the whole top 10
* for the query "sparkscan" was the ten framework copies of this page, with the
* SparkScan pages below all of them.
*
* Nothing is lost. What the table says - which product exists on which
* framework - is exactly what the graph's AvailableOn / NotAvailableOn edges
* carry, in a form a consumer can query rather than one it has to parse.
*/
const knowledgeIndexDeduped: string[] = [
"docs/features-by-framework.mdx",
...llmsNonWebSdkRoots.map((root) => `docs/${root}/features-by-framework.mdx`),
"docs/sdks/web/features-by-framework.mdx",
];

// Paths are matched by docusaurus-plugin-llms relative to siteDir (e.g. docs/...).
const llmsIgnoreFiles: string[] = [...assistantIgnoreFiles, ...llmsDedupedToWeb];
const knowledgeIndexIgnoreFiles: string[] = [...assistantIgnoreFiles, ...knowledgeIndexDeduped];

// ---------------------------------------------------------------------------
// SINGLE SOURCE OF TRUTH: docs versions and every `docusaurus_tag` derived
// from them.
Expand Down Expand Up @@ -465,6 +514,30 @@ const config: Config = {
// reader on 6.28.11 finds the 6.28 API and never the 8.x one.
apiReferenceTagsByVersionTag: buildApiReferenceTags(docsVersions),
versionNumberByTag: buildVersionNumberByTag(docsVersions),
// The CURATION half of the llms ignore list - what no AI consumer should be
// given - exported so the knowledge-extractor applies the same decision
// rather than a paraphrase of it. Exporting only `llmsIgnoredSdkTrees` left
// the two disagreeing about 39 modules; exporting the WHOLE llms list
// over-corrected, because it also carries a corpus-shape decision (see
// llmsDedupedToWeb) that a routing index must not inherit.
// Neither assistantIgnoreFiles nor knowledgeIndexIgnoreFiles is exported
// here. Each consumer takes its own list as a plugin option instead, so
// there is one name per consumer rather than a customFields key that has to
// be kept in step with it.
//
// This does NOT keep them out of the client bundle, and it is worth being
// exact about that: Docusaurus serialises the resolved config, plugin
// options included, so the globs ship either way - the built main.js
// carries two `ignoreFiles:` arrays. Moving them was a naming fix, not a
// size one.
// Which version THIS build serves at the root. Read by the
// knowledge-extractor plugin, which cannot otherwise tell a frozen version
// served at the root from `current` - and the two need opposite handling.
lastVersion: effectiveLastVersion,
// Read by the knowledge-extractor, which must NOT emit its ~22 MB of JSON
// into a preview: pr-preview-action commits the whole build/ into gh-pages,
// so every preview push would add those blobs there permanently.
isPreviewBuild,
},

// Set the production url of your site here
Expand Down Expand Up @@ -754,6 +827,48 @@ const config: Config = {
// gate can check a real build artifact instead of re-deriving the same
// assumption from this file and agreeing with itself.
searchTagsManifestPlugin,
// Build-generate AI layer: emits /assets/knowledge-retrieval-index.json and
// /assets/knowledge-graph.jsonld from the rendered HTML (see
// src/plugins/knowledge-extractor).
//
// Division of labour with docusaurus-plugin-llms above, which also exports for
// AI consumers: that plugin ships the PROSE (llms.txt as a link index,
// llms-full.txt as the text - 0.08 and 2.28 MiB on this build). This one ships
// TYPED METADATA AND EDGES for deciding what to read - intents, audiences,
// channels, frameworks, products, cites-API, see-also, availability - with
// only an excerpt of the prose, because the text is already published next to
// it (10.41 MiB index, 11.62 MiB graph). Neither replaces the other.
//
// They share assistantIgnoreFiles, so what an assistant may SEE is decided
// once. They differ, on purpose, on llmsDedupedToWeb: a flat corpus wants one
// copy of prose repeated across frameworks, a routing index needs the module
// that points at the framework the reader is actually on.
//
// Last in the array by convention, NOT by guarantee. Docusaurus runs every
// plugin's postBuild through Promise.all (core/lib/commands/build.js), so
// array position implies nothing about execution order and this hook races
// any other postBuild.
//
// There are THREE other postBuild hooks, not one: stripPreviewMediaPlugin,
// @docusaurus/plugin-client-redirects and docusaurus-plugin-llms.
//
// stripPreviewMediaPlugin deletes under build/img, which walkHtml skips, and
// docusaurus-plugin-llms only writes its own .txt files at the root. But
// plugin-client-redirects WRITES <path>/index.html stubs into outDir - the
// very files walkHtml enumerates - so this plugin sees a different file count
// depending on who wins. The extractor now identifies those stubs by their
// meta refresh and excludes them from both terms of its drift ratio, so the
// outcome no longer depends on the race.
//
// A future postBuild that REWRITES page html would still race
// non-deterministically and must be sequenced explicitly, not moved above
// this line.
// The curation list plus this index's own corpus-shape exclusions (see
// knowledgeIndexDeduped), passed as a plugin option so this consumer reads it
// under one name - the same shape docusaurus-plugin-llms uses above. The
// plugin throws if it is missing rather than defaulting to "index
// everything", which is the property that matters here.
[knowledgeExtractor, { ignoreFiles: knowledgeIndexIgnoreFiles }],
],

presets: [
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"docs:gate:setup": "node scripts/setup-vale.cjs",
"verify:search-tags": "node scripts/verify-search-tags.cjs",
"test:search-facets": "node scripts/test-search-facets.cjs",
"docs:retrieval-evals": "node scripts/retrieval-evals/run.cjs",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking for merge, not a code defect: fresh conflict with main. git merge-tree $(git merge-base origin/main HEAD) origin/main HEAD on this head (b5c57d3) shows a real conflict right here - main's #430 (same author, merged at 804180a, ~37 min after this branch's last commit) added verify:frameworks/test:frameworks/test:docs-gate at the same insertion point this branch adds docs:retrieval-evals. gh api pulls/418 confirms mergeable: false, mergeable_state: "dirty", checked twice a few minutes apart. CI on the current head is green, so this is a rebase, not a regression - same shape of conflict as the two earlier rounds in this PR, just a fresh instance because main advanced again since the last sync.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fresh instance of the recurring conflict, confirmed with git merge-tree --write-tree origin/main HEAD (git's real merge algorithm): main added test:robots at the same insertion point this branch adds docs:retrieval-evals. Mechanical to resolve, keep both script lines, but it's blocking the merge button right now and needs a rebase.

"verify:frameworks": "node scripts/verify-frameworks.cjs",
"test:frameworks": "node scripts/test-frameworks.cjs",
"test:docs-gate": "node scripts/test-docs-gate.cjs",
Expand All @@ -32,9 +33,11 @@
"@docusaurus/plugin-client-redirects": "3.4.0",
"@docusaurus/preset-classic": "^3.4.0",
"@mdx-js/react": "^3.0.0",
"cheerio": "1.0.0-rc.12",
"classnames": "^2.5.1",
"clsx": "^1.2.1",
"docusaurus-plugin-sass": "^0.2.5",
"gray-matter": "4.0.3",
"prism-react-renderer": "^2.1.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
Expand Down
8 changes: 8 additions & 0 deletions scripts/retrieval-evals/baseline.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"page_success_at_k": 0.8596,
"page_mrr": 0.775,
"pages": 520,
"modules": 4228,
"k": 3,
"recorded_at": "2026-09-21"
}
25 changes: 25 additions & 0 deletions scripts/retrieval-evals/gold-set.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"_comment": "Retrieval-eval gold set. Each entry is a real user query (seeded from Algolia top-queries + the product/intent taxonomy) mapped to the URL/path substrings a correct answer should live under. A retrieved knowledge module counts as relevant if its `url` contains ANY of `expect`. Add entries as new products/pages land; keep queries in the user's words.",
"queries": [
{ "query": "barcode capture get started", "expect": ["/barcode-capture/get-started"] },
{ "query": "sparkscan", "expect": ["/sparkscan/"] },
{ "query": "spark scan get started", "expect": ["/sparkscan/get-started", "/sparkscan/intro"] },
{ "query": "matrixscan count", "expect": ["/matrixscan-count/"] },
{ "query": "matrixscan find", "expect": ["/matrixscan-find/", "/matrixscan-pick/"] },
{ "query": "label capture", "expect": ["/label-capture/"] },
{ "query": "smart label capture", "expect": ["/label-capture/"] },
{ "query": "id capture supported documents", "expect": ["/id-capture/supported-documents", "/id-capture/", "id-documents"] },
{ "query": "id bolt", "expect": ["/id-bolt/"] },
{ "query": "parser", "expect": ["/parser/"] },
{ "query": "barcode selection", "expect": ["/barcode-selection/"] },
{ "query": "add the sdk", "expect": ["/add-sdk"] },
{ "query": "how to initialize data capture context", "expect": ["/add-sdk", "/get-started"] },
{ "query": "supported symbologies", "expect": ["barcode-symbologies", "symbology-properties"] },
{ "query": "qr code scanning", "expect": ["barcode-symbologies", "/barcode-capture/"] },
{ "query": "data matrix", "expect": ["barcode-symbologies", "symbology-properties"] },
{ "query": "ai powered barcode scanning", "expect": ["ai-powered-barcode-scanning"] },
{ "query": "scandit express inventory count", "expect": ["/express/", "inventory-count"] },
{ "query": "release notes", "expect": ["/release-notes"] },
{ "query": "agent skills", "expect": ["/agent-skills"] }
]
}
Loading
Loading