Corpus Hub: ask a pinned corpus
The Corpus Hub is a catalogue of reference corpora (other projects' source code, standards, advisory feeds), each pinned to one commit and licence-reviewed by a person. CI builds every corpus into a pack: an index that is already built, signed with the hub key. You ask a question with one command. XERJ downloads the pack the first time, checks its sha256 and its ed25519 signature before it extracts anything, restores it into a local node, and answers. Your machine never clones or indexes the corpus.
$ xerj ask --def LZ4_VERSION_RELEASE lz4 pin 2026-10-02 (15 days old) mixed: BSD-2 (lib) / GPL-2.0+ (rest) lz4/lib/lz4.h:133 #define LZ4_VERSION_RELEASE 0 /* for tweaks, bug-fixes, or development */ value: 0 approach-only licence (mixed: BSD-2 (lib) / GPL-2.0+ (rest)): read, do not copy answered from: local pack warning: approach-only licence (mixed: BSD-2 (lib) / GPL-2.0+ (rest)): read, do not copy
Every answer names the corpus, the pinned revision and its age, the licence, and the
file:line it rests on. Retrieval is lexical (BM25 over the words and
identifiers in the source), not semantic: ask with the names the source uses.
Which binary this page describes. This page applies to the released
xerj v1.0.0-rc.97 and later. That release has
xerj ask, xerj hub and the built-in hub key,
and on 2026-10-21 its xerj hub sync fetched and verified the production
signed catalogue (324 corpora). The earlier v1.0.0-rc.96 release has
xerj ask and xerj hub but no hub key: there,
every pull exits 5 and downloads nothing. To check your binary, run
xerj hub --help | grep -c build-catalogue; it prints
1 on a binary this page applies to and 0 on
v1.0.0-rc.96. A binary that prints 1 but has no key still exits 5 on the first pull, with the
message under When it does not work. The example outputs were
captured from main at b3753ddb against a
rehearsal hub (a catalogue of lz4, snappy and zstd signed with a test key), before the production
catalogue was served. Pack sizes, ages and process IDs on your machine will differ; the formats
will not.
Source · engine/crates/xerj-hub/src/ (cli.rs, ask.rs, sync.rs, node.rs) · spec docs/CORPUS_HUB_V2_IMPLEMENTATION.md · browse: hub.xerj.org
Hub, local, or neither
Pick the source before you search:
HUB public material you cannot quote exactly: a library's source at a
pinned commit, a standard, a regulation, an advisory feed.
xerj ask "<question>"
LOCAL files only this machine has: your project, private documents, exports.
xerj autoindex <folder> --prefix <name> (once)
xerj search "<text>" --prefix <name> (pass the same --prefix)
Without --prefix, search and def read the default `ax` prefix.
NEITHER a fact you can already state exactly. Retrieval adds cost there.
What this build does, and what it does not
- Your question stays on this machine. This build has no hosted-answer client.
It downloads two things, the signed catalogue and the packs you use, and sends no question text,
no document text and no credential to the hub.
--hosted onprintshosted answers were requested but this build has no hosted client; answering from a local packand answers locally. - Auto-pull cap: 50 MiB per pack (
AUTO_PULL_MAX_BYTES, 52,428,800 bytes). A question does not auto-download a larger pack; an explicitxerj hub pull <slug>does fetch it, bounded by the 4 GiB disk budget (DISK_BUDGET_BYTES, the largest pack the cache can hold). - Licences. A corpus with the licence class
excludedis never pulled. Anapproach-onlycorpus is pulled, and every hit carriesread, do not copy. - Disk. The pack cache is pruned to 4.0 GB, least recently used first.
- No lockfile yet. The spec's
xerj.lockis not written by this build, and--locked,xerj hub updateandxerj hub rmdo not exist. To get the same answer on two machines, compare the pin in the answer's header line (orpin_shain--json). - MCP tools never download. The three hub tools answer from packs already on the machine and from the catalogue already cached; see MCP.
Ask your first question
You need the xerj binary and network access to
hub.xerj.org and to GitHub release downloads. You do not need to start
a node: if nothing answers on localhost:9200, XERJ starts one of its own
(see Which node holds the packs).
1. Fetch the catalogue.
$ xerj hub sync hub sync: catalogue refreshed (generated 2026-10-17T23:35:36Z, 3 corpora) hub sync: no cached packs to update hub sync: 0 updated, 0 current, 0 kept, 0 failed, 0 pruned
Verify: echo $? prints 0, and
xerj hub status ends with catalogue: 0 days old.
xerj ask also fetches the catalogue when none is cached, so this step is
optional in a shell. It is required before the MCP tools can answer.
2. Ask for a definition. --def is for a value, a
signature or a default. It prints the declaration and, for a constant or a macro, a
value: line, and no passages. The first answer from a corpus pulls its
pack:
$ xerj ask --def ZSTD_CLEVEL_DEFAULT xerj hub: pulling 'zstd' (22.1 MB) — one-time download, later questions answer locally xerj hub: pulled zstd 01b7154f 23137348 → 5 indices, 7874 definitions zstd pin 2026-10-02 (15 days old) mixed: BSD-3 (lib) / GPL-2.0 (cli) zstd/lib/zstd.h:134 # define ZSTD_CLEVEL_DEFAULT 3 value: 3 approach-only licence (mixed: BSD-3 (lib) / GPL-2.0 (cli)): read, do not copy answered from: local pack warning: approach-only licence (mixed: BSD-3 (lib) / GPL-2.0 (cli)): read, do not copy
Verify: the output contains answered from: local pack and
echo $? prints 0. The two
xerj hub: lines go to stderr and appear only on the run that downloads.
xerj ask "what is the value of ZSTD_CLEVEL_DEFAULT" and
xerj ask ZSTD_CLEVEL_DEFAULT (a lone identifier) ask the same
question.
3. Ask how something works. Name the corpus with
--corpus when the question is in plain words (see
How a corpus is chosen):
$ xerj ask --corpus lz4 --max-tokens 120 "frame decoder"
lz4 pin 2026-10-02 (15 days old) mixed: BSD-2 (lib) / GPL-2.0+ (rest) lz4/lib/lz4frame.c:1644
size_t LZ4F_decompress(LZ4F_dctx* dctx,
void* dstBuffer, size_t* dstSizePtr,
const void* srcBuffer, size_t* srcSizePtr,
…
approach-only licence (mixed: BSD-2 (lib) / GPL-2.0+ (rest)): read, do not copy
answered from: local pack
warning: approach-only licence (mixed: BSD-2 (lib) / GPL-2.0+ (rest)): read, do not copy
Verify: the first line starts with lz4 pin and ends with a
file:line. Without --max-tokens you get up
to five passages. --files lists the matching files instead of
passages.
4. See what is on disk.
$ xerj hub status pack: lz4 0774d055 15 days old 8.1 MB last used today pack: zstd 01b7154f 15 days old 26.5 MB last used today node: running pid 2789576 port 9300 hosted: auto last sync: today catalogue: 0 days old
Verify: one pack: line for each corpus you asked.
5. Stop the node XERJ started when you are done:
$ xerj hub stop stopped the hub node (pid 2789576)
Verify: a second xerj hub stop prints
no hub node was started by xerj; nothing to stop. It stops only the
process xerj ask recorded. Never pkill xerj
or killall xerj: other XERJ nodes may be running on the machine. The
packs stay on disk; the next xerj ask starts the node again and
answers without a download.
Reading an answer
<corpus> pin <date> (<N> days old) <licence> <file>:<line> one header per hit <the declaration, or the passage> value: <literal> def mode, constants and macros <per-hit licence warning> answered from: local pack warning: <…> zero or more
pin is the source revision the pack was built from. Check its age
when the question is about the current version of a project. Tell the reader which corpus, pin
date and licence the answer rests on. warning: approach-only licence
means read the source and write your own code; do not copy it.
--json prints the same answer as one object:
$ xerj ask --json --def LZ4_VERSION_RELEASE
{"corpus":"lz4","pin_sha":"0774d05537f9762f838f7ab541b7765f1a729cb5","pin_date":"2026-10-02",
"age_days":15,"licence_spdx":"mixed: BSD-2 (lib) / GPL-2.0+ (rest)","licence_class":"approach-only",
"answered_from":"local_pack","mode":"def","hits":[{"file":"lz4/lib/lz4.h","line":133,"kind":"const",
"text":"#define LZ4_VERSION_RELEASE 0 /* for tweaks, bug-fixes, or development */","value":"0",
"score":14.069364,"licence_warning":"approach-only licence (mixed: BSD-2 (lib) / GPL-2.0+ (rest)): read, do not copy"}],
"truncated":false,"warnings":["approach-only licence (mixed: BSD-2 (lib) / GPL-2.0+ (rest)): read, do not copy"]}
(Wrapped here for width; the binary prints one line.) Verify:
xerj ask --json --def LZ4_VERSION_RELEASE | jq -r .hits[0].value
prints 0.
How a corpus is chosen
--corpus <slug> skips routing. Without it, XERJ scores each
corpus in the cached catalogue from the dependencies of the project you are in
(Cargo.lock, package.json,
go.mod, requirements.txt,
pyproject.toml), from the catalogue's per-corpus term sketch, and from
the exact symbol a definition question names. Three outcomes:
# one corpus fits: it answers (exit 0) # more than one fits: the candidates print, one per line (exit 4) $ xerj ask XXH64 more than one corpus matches; re-run with --corpus <slug> lz4 — an agent needing fast block compression queries this for the block format and decoder loop zstd — an agent tuning compression queries this for frame/block formats, window and level internals # nothing scores high enough (exit 1) $ xerj "how does the lz4 frame decoder work" no hub corpus matches; see xerj.org/llms.txt closest corpora: lz4, snappy, zstd; re-run with --corpus <slug> to ask one of them
A question in plain words often does not route by itself, even when it contains the
project's name, as the last example shows. When you see closest corpora:,
run the question again with --corpus. When there is no
closest corpora: line, the hub does not hold the material: say so,
and do not answer from memory.
xerj "<question>" is the same command as
xerj ask "<question>" when the question contains a space and its
first word is not a subcommand. For a single word, write xerj ask <word>.
Which node holds the packs
A pack is restored into a running XERJ node. xerj ask uses the
first of --url, XERJ_URL and
http://localhost:9200. If that node answers, it is used as it is. If it
does not answer and is a loopback address, XERJ starts its own node on the first free port from
9300, with its data in ~/.xerj/hub/node, and records its PID:
xerj hub: no node answers at http://127.0.0.1:29599; starting a local node on port 9300 (data: /home/you/.xerj/hub/node, log: /home/you/.xerj/hub/node.log) xerj hub: node ready on port 9300 (pid 2789576)
A non-loopback --url that does not answer is an error; no local
node is started in its place.
Using a node you started yourself. The engine restores a snapshot only from
its own data directory or from a directory named in
[limits] snapshot_repo_allowlist. Packs are extracted under
~/.xerj/hub/packs, so a node started without that setting refuses the
restore:
$ xerj hub pull lz4
xerj hub pull: restore snapshot: POST /_snapshot/hub-lz4/lz4-0774d055/_restore?wait_for_completion=true -> HTTP 400: {"error":{…"reason":"snapshot repository location [/home/you/.xerj/hub/packs/lz4/0774d055/snapshot] is outside data_dir and not in limits.snapshot_repo_allowlist; refusing (set the allowlist to permit external snapshot roots)"…}
Fix: add the pack directory to the node's config file, as an absolute path (TOML does not
expand ~), and restart the node with --config:
$ printf '[limits]\nsnapshot_repo_allowlist = ["%s/.xerj/hub/packs"]\n' "$HOME" > ~/xerj-hub.toml $ nohup xerj --insecure --data-dir ~/xerj-data --config ~/xerj-hub.toml > ~/xerj-node.log 2>&1 & echo $! > ~/xerj-node.pid $ xerj hub pull lz4 pulled lz4 0774d055 5810327 → 2 indices, 1702 definitions
Verify: the last line starts with pulled lz4 (or
lz4: already present on a second run). Stop that node with
kill $(cat ~/xerj-node.pid). For a node with auth on, export
XERJ_API_KEY (the key is in <data-dir>/admin.key).
xerj code does not start a node.
xerj code <slug> --def <SYMBOL> and
xerj code <slug> "<query>" read a pulled pack too, but
only from --url, XERJ_URL or
localhost:9200. If the pack lives in the node
xerj ask started, pass its port (from
xerj hub status), or use xerj ask --corpus <slug>:
$ xerj code lz4 --def LZ4_MAX_INPUT_SIZE --url http://127.0.0.1:9300 lz4 pin 2026-10-02 (15 days old) mixed: BSD-2 (lib) / GPL-2.0+ (rest) lz4/lib/lz4.h:214 #define LZ4_MAX_INPUT_SIZE 0x7E000000 /* 2 113 929 216 bytes */ value: 0x7E000000 approach-only licence (mixed: BSD-2 (lib) / GPL-2.0+ (rest)): read, do not copy answered from: local pack
Verify: exit 0 and a value: line. Pointed at a node without the
pack, the same command prints defs query failed: … and
no match in lz4, and exits 1.
Offline, no download, and the size cap
$ xerj ask --offline --corpus snappy "varint decoding" no local pack for 'snappy' and --offline forbids downloading it; run: xerj hub pull snappy (14.3 MB) $ xerj ask --no-fetch --corpus zstd --def ZSTD_CLEVEL_DEFAULT no local pack for 'zstd' and --no-fetch forbids downloading it; run: xerj hub pull zstd (22.1 MB)
Both exit 1. --offline never touches the network: it reads the
cached catalogue and cached packs only, and a corpus already on disk answers normally.
--no-fetch does not download a pack. Run the printed line, then ask
again:
$ xerj hub pull snappy pulled snappy 9c28114a 15034490 → 3 indices, 382 definitions
Verify: pulled <slug> <pin8> <bytes> → <n> indices, <defs> definitions
and exit 0.
For a pack over the 50 MiB auto-pull cap, xerj ask prints
'<slug>' is <MB> MB, over the 50.0 MB auto-pull cap, so a question does not download it automatically and no hosted answer is available; run `xerj hub pull <slug>` to fetch it once, then ask again
and exits 1. The explicit xerj hub pull <slug> does fetch it, bounded by the 4 GiB disk budget (DISK_BUDGET_BYTES), so the corpus is usable once pulled; only a pack larger than the whole disk budget is refused.
The hosted setting
--hosted on|off|auto on xerj ask, or
xerj hub config hosted on|off|auto stored in
~/.xerj/hub/config.toml, decides whether a question may be sent to a
hosted hub node. auto, the default, is off inside a git repository
(the first time, XERJ prints hosted answers are off in git repositories; xerj hub config hosted on)
and on elsewhere. No public hosted node exists, and this build has no hosted client, so every
answer comes from a local pack whatever the setting.
$ xerj hub config hosted off hosted = off
Verify: xerj hub status shows hosted: off.
From an MCP client
xerj mcp serves three hub tools next to its other tools. They run
the same flow as xerj ask, through the node that answers on
--url / XERJ_URL or else the node XERJ
starts:
xerj_hub_ask {question, corpus?, mode?: "def"|"how"|"files", max_tokens?} one turn, like xerj ask
xerj_hub_def {slug, symbol, k?} like xerj ask --corpus <slug> --def <SYMBOL>
xerj_hub_find {question, k?} which corpora could answer; no passages
A hub tool never downloads a pack and never fetches the catalogue. A tool call
must not wait on the network. Run xerj hub sync in a shell once, and
xerj hub pull <slug> for each corpus, then call the tool. Until
then the tool returns an error (isError: true) that says which command
to run:
no hub catalogue is cached on this machine, and hub tools never fetch one (a tool call does not wait on the network); run `xerj hub sync` in a shell once, then call the tool again (…) no local pack for 'lz4' and --no-fetch forbids downloading it; run: xerj hub pull lz4 (5.5 MB)
With the pack on disk, xerj_hub_def {"slug":"lz4","symbol":"LZ4_VERSION_RELEASE"}
returns the same text as the shell answer, and the structured content is the
answer JSON. xerj_hub_find {"question":"XXH64"} returns:
lz4 — an agent needing fast block compression queries this for the block format and decoder loop (score 0.45, pin 2026-10-02, mixed: BSD-2 (lib) / GPL-2.0+ (rest), cached)
zstd — an agent tuning compression queries this for frame/block formats, window and level internals (score 0.45, pin 2026-10-02, mixed: BSD-3 (lib) / GPL-2.0 (cli), cached)
routing: ambiguous; ask one with xerj_hub_ask {question, corpus}
Register it once, with the absolute path of the binary (an editor started from
a desktop icon does not inherit your shell's PATH;
command -v xerj prints it), then restart the session:
# Claude Code
claude mcp add --scope user xerj -- "$(command -v xerj)" mcp
# .mcp.json (Claude Code project scope; `xerj init` writes it), Cursor, Windsurf,
# Claude Desktop, Cline: root key mcpServers
{"mcpServers": {"xerj": {"command": "/abs/path/to/xerj", "args": ["mcp"]}}}
# VS Code, .vscode/mcp.json: root key servers (a block under mcpServers is ignored)
{"servers": {"xerj": {"type": "stdio", "command": "/abs/path/to/xerj", "args": ["mcp"]}}}
Verify without a client: this prints xerj_hub_ask once.
$ printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"check","version":"0"}}}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' | xerj mcp 2>/dev/null | grep -o '"name":"xerj_hub_ask"'
"name":"xerj_hub_ask"
A build with no hub key serves 13 tools and omits the three hub tools. The full schemas are in mcp-tools.json; the endpoint contract lists them with the other agent operations.
Keeping packs current
$ xerj hub sync hub sync: catalogue refreshed (generated 2026-10-17T23:35:36Z, 3 corpora) hub sync: lz4 0774d055: current hub sync: 0 updated, 1 current, 0 kept, 0 failed, 0 pruned
xerj hub sync refreshes the catalogue, pulls the new pin of every
cached pack whose pin moved (sha256 and signature are checked first), removes the old pin only
after the new one is restored, prunes to the disk budget, and writes
~/.xerj/hub/last_sync. A failed update keeps the old pin serving.
Verify: exit 0 and 0 failed on the last line.
$ xerj hub sync --install-timer /home/you/.config/systemd/user/xerj-hub-sync.timer
This writes a systemd user timer and service (Linux) or a launchd agent (macOS) that runs
xerj hub sync every 6 hours, and prints the path. It enables, loads
and starts nothing: the comment at the top of the written file says how. Verify: the printed file
exists. Hub packs are never refused as stale; the age is in every answer's header, and a newer pin
arrives with sync.
Exit codes
xerj ask 0 answered · 1 no match, nothing to answer from, or a pull/node failure
2 usage error or unknown corpus · 3 a pack failed verification
4 more than one corpus matches (candidates printed; re-run with --corpus)
5 this build has no hub public key: nothing was downloaded
xerj hub pull 0 pulled or already present · 1 anything else (excluded licence and
over-the-cap packs included) · 2 unknown corpus · 3 verification failed
5 no hub public key
xerj hub sync 0 synced or nothing to do · 1 catalogue not refreshed or a pack not
updated (old pin kept) · 2 usage error · 3 a pack failed verification
Exit 3 means the bytes did not match: nothing was extracted, and a retry downloads the same bytes. Report it rather than retrying. Exit 5 cannot be fixed by a retry either.
Where things live
~/.xerj/hub/catalogue.json, .sig the cached, signed catalogue (refreshed after 24 h, or by sync)
~/.xerj/hub/packs/<slug>/<pin8>/ extracted packs (pack.json, SHA256SUMS, snapshot/, last_used)
~/.xerj/hub/node/ data of the node xerj ask starts; node.log, node.pid,
node.port and node.toml beside it
~/.xerj/hub/config.toml the hosted setting
~/.xerj/hub/last_sync written by a sync with no failures
XERJ_HUB_HOME moves the whole directory.
XERJ_HUB_URL points the client at another hub (default
https://hub.xerj.org; the catalogue is read from
<hub>/api/v1/catalogue.json and its .sig).
Both are listed on the environment page.
When it does not work
The catalogue is unavailable. A static host can answer an unknown path with an HTML page and status 200. The client checks the content and the signature, and refuses it:
$ xerj hub sync xerj hub sync: cannot refresh the catalogue; nothing was changed: the hub catalogue is unavailable: https://hub.xerj.org/api/v1/catalogue.json returned 264903 bytes starting "<!doctype html><html lang=\"en\"><head><me" (text/html; charset=utf-8), not a signed catalogue; no catalogue is served at this address
Exit 1, and a cached catalogue (if any) is kept. xerj ask and
xerj hub pull print the same reason after
cannot load the hub catalogue:. Packs already on disk still answer with
--offline.
A pack or the catalogue fails verification.
$ xerj hub pull snappy xerj hub pull: pack verification failed: pack sha256 mismatch: archive is 97351ed6…, expected ae10dc7d… $ xerj hub sync xerj hub sync: cannot refresh the catalogue; nothing was changed: catalogue signature did not verify; refusing to replace the cache: …
The pull exits 3 and extracts nothing; the sync exits 1 and keeps the previous catalogue. (Both were produced by changing one byte of a served file.)
No hub key in this build.
$ xerj ask --corpus snappy "varint" pull of 'snappy' failed: no hub public key is configured in this build (or XERJ_HUB_PUBKEY=none), so a pack's signature cannot be verified; nothing was downloaded
Exit 5. Use a binary that has the key (see the check at the top of this page). For your own
files, xerj autoindex works in every build.
Contributing a corpus
The registry lives on the
corpus-hub
branch of xerj-org/xerj, and contributions arrive as pull requests against that branch. Its
CONTRIBUTING.md
is the source of record for the rules; this section is the walkthrough. Browse the registry at
hub.xerj.org.
You contribute a build input, never a pack. A manifest or a recipe goes into
git. CI (.github/workflows/hub-packs.yml) builds the pack with
xerj hub build-pack, signs it with the hub key, pulls it back on a
fresh node and checks one --def and one question before it publishes
the release, then rebuilds and signs the catalogue with
xerj hub build-catalogue. Users download the signed pack; they do not
rebuild the corpus from your manifest.
Two lanes
LANE A — reference corpus tools/xerj-code/hub/<name>.json (a manifest) LANE B — record pack tools/packs/<name>/recipe.toml (a recipe)
A reference corpus is other people's source code at pinned commits, the
material for reference coding ("how do three other engines implement merge policy?"). A
record pack is structured data you curate and resolve, for example Rust
vulnerability advisories identity-merged across osv.dev and RustSec. If your content is "files
someone can clone", it is Lane A; if it is "records distilled from one or more sources", it is
Lane B. The catalogue that xerj ask reads lists Lane A corpora; the
record packs are published separately by pack-publish.yml
(below).
Lane A — the manifest
Copy tools/xerj-code/hub/TEMPLATE.json to
tools/xerj-code/hub/<your-corpus>.json. The corpus name must equal
the filename. Field by field:
{
"corpus": "xerj-storage",
"cloned_at": "<UTC timestamp the build ran>",
"repos": [
{
"repo": "sled",
"url": "https://github.com/spacejam/sled",
"licence": "<what the detector read — do not trust this alone>",
"sha": "<full 40-hex commit the build actually used>",
"files": 417, "bytes": 2900000,
"review": {
"spdx": "<SPDX id YOU confirmed by opening the licence file>",
"use": "adapt-with-attribution",
"by": "your-handle", "at": "2026-10-01",
"note": "<one line: what you opened, what you concluded>"
}
}
]
}
The two fields CI cannot check, and a reviewer will hold you to: sha
must be the commit the build actually used (never a branch head), and
review must be a human's verdict (see
the licence contract). The sha becomes the
pack's pin, the pin every answer prints.
Lane B — the recipe
Copy tools/packs/TEMPLATE-recipe.toml to
tools/packs/<name>/recipe.toml. Everything domain-specific lives
in the recipe, never in the tooling:
[recipe] format = 1 · name = directory name · description
[[sources]] one per source: slug, kind (git|http-zip|dir), url,
glob, format (flat|osv|rustsec-md), licence
[envelope] how a raw source file becomes one searchable record:
id_from, title_from, body_join, defs_from
[identity] which fields make two records the same thing
(edges, each=true explodes list fields; union-find
closes the chain) + canonical_source_order
[merge] precedence when two sources carry the same fact
[[derived]] optional build-time fields (regex_extract, present)
[emit] shards
The identity block is the whole dedup contract: write it so a reader can predict the merged
count, then put the measured count (envelopes → records) in your README. The showcase to read
alongside the template is tools/packs/rust-vulns/recipe.toml. Built
packs are never committed; a maintainer builds from your recipe and attaches the
signed pack to a GitHub Release.
The licence contract
Every source carries a human review block with one of three verdicts:
adapt-with-attribution Apache-2.0 · MIT · BSD — cite file:line when
you adapt; copying is fine within the licence
approach-only AGPL · SSPL · Elastic · BUSL · GPL — read the
design, write your own code, never paste
mixed per-file split (e.g. MIT core + BUSL edition) —
name the boundary in the note
The verdict is what xerj ask prints: an approach-only or mixed
corpus carries read, do not copy on every hit. Our licence detector once
reported Elasticsearch as Apache-2.0, because the AGPL text contains the phrase «an "Apache License
2.0" compatible license». A detector's false "permissive" is how restricted code ends up pasted
into an Apache-2.0 project. CI checks that a review block exists and that its fields are
well-formed; only the reviewer's own reading of the licence file makes it true.
The pull request
# 1. branch FROM corpus-hub (not main) so the CI workflow is in your PR git clone -b corpus-hub https://github.com/xerj-org/xerj && cd xerj # 2. add your manifest or recipe, then validate locally (stdlib only) python3 tools/xerj-code/hub/validate_hub.py # 3. commit, push, open the PR against corpus-hub git checkout -b corpus/<your-name> git add tools/xerj-code/hub/<your-name>.json git commit -m "corpus: add <your-name> (<domain>)" git push -u origin corpus/<your-name>
CI on every push and PR runs the same validator plus two registry guards: no file over 5 MB
under the registry paths (built packs and key material must not be in git), and no private key
material under tools/packs/keys. A maintainer then reviews domain fit,
pin truthfulness and, above all, the licence verdicts, and merges. After the merge, CI builds the
pack; a pack whose consumer check fails stays a draft and is not catalogued.
Is your corpus a fit?
1. Can you name the domain? Finish the sentence: "an agent working on ___
would query this for ___." That sentence becomes the corpus's ask_for
line, which xerj ask prints when it lists candidates. A corpus that
contains everything retrieves like a search engine with no query.
2. Is the content un-memorised? Retrieval helps most on niche, private, internal and post-cutoff material. On famous public library code a model already knows, retrieval is overhead. Do not force it where it loses.
Your own corpora. For a set of repositories only you need,
xerj corpus add <name> <git-url>… (or
--from a manifest file you wrote) and
xerj corpus index <name> still clone and index on your machine,
and xerj code <name> searches the result. That is a local path
and does not use the hub.