Skip to content

feat(autoindex): estimate on the user's machine, ask before a long run, index what matters first - #281

Merged
xerj-org merged 3 commits into
mainfrom
feat/ux-estimate-gate
Aug 11, 2026
Merged

feat(autoindex): estimate on the user's machine, ask before a long run, index what matters first#281
xerj-org merged 3 commits into
mainfrom
feat/ux-estimate-gate

Conversation

@xerj-org

@xerj-org xerj-org commented Aug 10, 2026

Copy link
Copy Markdown
Owner

landing/llms.txt already tells an agent driving XERJ on someone's laptop to estimate, tell them, ask if it is big, report progress. That was advice, and advice is ignorable. This makes the binary do it.

"search should be smart with ai agent harness getting real important things to index first and make it clear for user that indexing will take time if many files, so xerj autoindex if estimated more than 10min work needs to ask AI back what to do" — owner

Built on #240 (resource policy) and #241 (progress surface); neither is re-derived here.

(a) Estimate, measured on the user's machine

Phase A already reads and parses every file to sniff and sample it, so it now times that work per format family:

estimate: at least 64.2 s–64.2 s — a MEASURED FLOOR for client-side extraction,
          not a prediction of the whole run: server indexing, embedding and
          network time are not in it
estimate: basis — measured on this machine during phase A: 567 file(s) /
          793.4 MB read end to end across 3 family/families, scheduled over
          1 worker(s). Covers 793.4 MB of the 793.4 MB planned (100%).
  estimate: code   500 files 749.3 MB at 11.7 MB/s measured over 500 file(s) → 64.2 s
  estimate: yaml    12 files  23.8 KB at 13.1 MB/s measured over  12 file(s) → 0.0 s

A family is priced only from files phase A provably read end to end — decided from each extractor's own contract, not assumed (whole-file parsers always; streaming parsers only under the sampling byte cap and short of the record cap; never sqlite, never gzip). Everything else is named under unmeasured_families with its bytes rather than priced at another family's rate, and coverage reports the share of planned bytes covered. No measurement → no number and no gate; basis says why. The two ends are the list-scheduling bounds (Graham 1969) over the phase-B worker count #240 chose.

It is a floor, and is labelled as one everywhere (kind, headline, excludes, --help, llms.txt). It covers client-side extraction only, because measuring the server, the network or embedding means writing to the index the estimate exists to ask permission for. Measured here: 68 MB source tree → floor 0.1 s vs real 8.9 s; 793 MB → 64.2 s vs ~350 s. So the gate under-asks and never over-asks, and the "not triggered" line says in words that silence is not a promise the run is short. Closing that gap with a multiplier would mean gating on a number nobody measured.

(b) The 10-minute gate

Past --max-minutes (default 10) with no answer: nothing is indexed, a JSON decision request goes to stdout, and the process exits 4 — its own code, because 1 is already the catch-all for every real failure and an agent must be able to tell "choose something" from "your endpoint is down".

The payload carries the estimate and basis, file/byte counts, the per-band work order, the heaviest directories with real byte counts (flagged when they match the vendored/generated rule), and four options. narrower is re-costed with the same measured rates. fast states no speed-up factor — it reports which datasets and how many files it changes and says plainly that this run did not measure the factor.

Answer with --approve proceed|fast|cancel (--yes = proceed); --max-minutes 0 disables it. --approve fast really applies --no-semantic --no-graph; --approve narrower is refused with instructions rather than accepted and silently ignored (#204). A person at a terminal gets the same facts as prose plus a prompt; a piped or agent-driven run never touches stdin, and an unanswerable prompt is a cancel, never consent. The gate sits before the first remote mutation.

(c) Index what matters first

The queue was sorted by size alone: right about scheduling, silent about value — a user who stopped early got whatever was largest, which on a source tree is node_modules. Work now drains by value band (source and documents → configuration → structured data → logs and line files → vendored/generated/minified), with the old biggest-first rule kept inside each band so a large file runs alongside its band instead of becoming the tail; a single-worker run goes smallest-first. One exception: a file whose extraction outlasts everything ranked above it (size × workers > the rest) starts first regardless of band. Bands, counts and the reason each sits where it does are printed with the plan and shipped as priority_order — an unexplained order is indistinguishable from an arbitrary one. Bytes-based progress is unaffected.

Verified live (branch binary, real node)

check result
1.1 GB / 747 files, --workers 1 --max-minutes 1 exit 4, payload on stdout, 0 gt-* indices and 0 brain-edge indices created
same run --approve fast gate bypassed, exit 0, body mapped text not semantic_text, no edges index
--approve cancel exit 0, reason=cancelled, nothing indexed
--dry-run --max-minutes 1 "a real run would STOP here and exit 4", does not stop; stdout still exactly the plan JSON
ordering, 28 MB mixed corpus, 2 workers first second of phase B on src/mod_*.rs; the 1.7 MB node_modules/** files — largest in the corpus, and first under the old rule — drained last
--approve narrower/later, --max-minutes 10081/soon, gate flags on map/status all exit 2 with an explanation

Gates

  • cargo fmt --all -- --check clean
  • cargo clippy -p xerj-autoindex -p xerj-server --all-targets -- -D warnings clean
  • cargo test -p xerj-autoindex390 passed / 0 failed
  • cargo test -p xerj-server --bins30 passed / 0 failed
  • ES-YAML conformance — 1366 passed / 0 failed / 3 skipped

Known limits (stated, not hidden)

  1. The estimate is a floor, not a prediction; the gate therefore misses runs whose cost is server-side. A measured server term needs a probe write, which would cost the "nothing of yours has been touched" property that makes stopping safe. Follow-up, not silently assumed away.
  2. A frozen-plan resume has no fresh measurement, so it prints "no estimate" and does not gate. Correct, but it means an interrupted long run is never re-gated.
  3. xerj brain composes autoindex and has no --approve, so it sets max_minutes: 0 explicitly (one hunk in xerj-server/src/brain.rs) — arming the gate there would stop the run with instructions its caller cannot follow.
  4. Band misclassification is possible (build, out, obj, dist are ambiguous names). The cost is a file's place in the queue and nothing else — it is still indexed, still complete.

Reference-coding note

The retrieval server was not reachable on localhost:9200, and the only live node carried an unrelated networking corpus, so I read the corpus clones in ~/.xerj-code/corpora/ directly rather than starting or reindexing anything: tantivy log_merge_policy.rs:94-99 (MIT — sorts Reverse(size) then buckets into levels; adapted, our levels are value bands), quickwit quickwit-cli/src/tool.rs:977-1012 and ClickHouse ProgressIndication.cpp:118-119,223-227 (both report measured bytes/s and stop short of an ETA), meilisearch milli::progress (named steps, no ETA at all). None of the four estimates a job before running it.


Rebased onto main (post-#259 incremental route)

main landed the generated/incremental reconcile path while this was in flight. Rebased and re-verified end to end on the rebased tree; all figures above were re-measured on it (exit 4, 0 gt-* indices, ordering unchanged, ES-YAML 1366/0/3, cargo test -p xerj-autoindex now 465 passed / 0 failed).

One interaction worth calling out: an incremental reconcile of an already committed generation (--no-graph re-run) publishes from a sealed snapshot and never reaches the phase-B queue the estimate prices, so --max-minutes cannot apply there. Rather than accept the flag and quietly not honour it (#204), that route now prints the exemption and points at --dry-run. Both phase-A routes feed the same throughput meter.

Pre-existing bug found while verifying (NOT from this PR)

A second --no-graph run over the same folder and state dir fails:

error: desired manifest digest does not match sync_begin payload   (exit 1)

Reproduced identically on unmodified origin/main @ 8fb08a9 with a binary built from a clean worktree — first run exits 0, second exits 1 — so it is a defect in the newly-landed incremental route, not a regression here. No flag of this PR is involved (--max-minutes 0, --max-minutes 10 and the default all reproduce, and none of the new fields enter prepared_records_identity). Worth its own issue.


Fix: --quiet made the gate's question invisible but still blocking

Found by measurement during the rebase verification above, and the single worst
failure mode this feature could have: an invisible prompt is indistinguishable
from a hang
, in the one feature whose entire job is to say "this will take a
while".

The gate's prose and its prompt echo both go through the progress surface
(Progress::note), which --quiet / --progress none silences. But
can_prompt() only checked that stdin and stderr were TTYs — never that the
question had actually been printed. So a person at a terminal who passed
--quiet was asked nothing and then waited on read_line forever. Agent-driven
runs were never affected (stdin is not a TTY, so no prompt was attempted).

Measured on a real pty against a real node, 370 MB / 540 files,
--workers 1 --max-minutes 1:

run before after
pty, --progress none still blocked at 220 s, 0 bytes ever printed (killed) exit 4 at 93.6 s, 4022 B decision-request JSON on stdout
pty, --quiet still blocked at 220 s, 0 bytes ever printed (killed) exit 4 at 93.9 s, same payload
pty, normal progress, answer c prompt shown, exit 0 cancelled, 113.9 s prompt shown, exit 0 cancelled, 93.7 s
stdin closed exit 4, 113.9 s exit 4, 94.0 s
--quiet --approve cancel exit 0, 0 bytes (the way out; silence kept)
pty, --progress json, answer f prompt still offered, answer honoured, exit 4

No ax-* index was created by any run — the gate still sits before the first
remote mutation.

Why route (a), and not (b)

Both routes were on the table: (a) skip the prompt when the question cannot be
printed, or (b) route the gate's prose and prompt to stderr independently of
progress mode. (a).

  1. --quiet has a documented contract in --help: "prints no progress and
    NO terminal line (only a fatal error: line)". Route (b) writes ~30 lines of
    prose plus an interactive prompt to a stream the user explicitly silenced.
    Accepting a flag and then not honouring it is the accepted-and-ignored class
    tracked in Tracking: accepted-and-ignored is the dominant bug class, six instances found in one review #204 — the same defect this PR already refuses elsewhere with
    --approve narrower.
  2. (a) is not a silent early exit. The decision request goes to stdout,
    which --quiet never claimed to silence (stdout is the RESULT, stderr is PROGRESS — the help says so). The user still gets every fact, in
    machine-readable form, plus exit 4. The only thing lost is the prose
    rendering of information they still receive — and prose is precisely what
    they opted out of.
  3. It makes quiet runs uniform with agent-driven ones. One rule to document
    and reason about, instead of a fourth distinct behaviour.
  4. (b) would newly let a --quiet run block on stdin, enlarging the set of
    configurations that can wait for a human. That is the hazard itself.

The honest objection to (a) — "a user who asked for quiet and got a silent early
exit may be equally confused" — is answered rather than waved away: the payload
now carries prompt_offered and prompt_not_offered_because, and the prose
prints the same sentence wherever a surface exists to print it. Every reason
names the way out (--approve …), so no path ends in "wait". Under --quiet
the user sees, on stdout:

"prompt_offered": false,
"prompt_not_offered_because": "no terminal prompt: --quiet / --progress none
  silences the question, and this run will never wait on stdin for something it
  did not print. The decision request below is on stdout, which --quiet does not
  silence. Re-run with --approve proceed|fast|cancel, or without --quiet to be asked"

Implementation

  • gate::NoPrompt + gate::prompt_blocked_by(stdin_tty, stderr_tty, question_visible)
    pure, so the whole rule is testable without a terminal (same shape as
    progress::resolve). question_visible is the third condition that was missing.
  • gate::answer_from_terminal(blocked, open_stdin, echo) takes stdin as a
    closure, so on a blocked path the process's stdin is never even opened.
    That is the invariant — not "no answer was returned" but "stdin was not read".
  • can_prompt() is removed, not kept: a function with that name that ignores
    the surface is exactly the footgun that caused this.

Regression test

gate::tests::a_question_that_cannot_be_seen_is_never_asked hands in a reader
that panics if anything touches it. Verified to fail without the fix, on both
halves of it:

  • classifier reverted to the tty-only rule → fails the QuestionSilenced assertion
  • guard reverted → panics stdin was read for a question the user was never shown

Documented

--help (the --quiet entry and the ESTIMATE + DECISION GATE section) and
landing/llms.txt now state the three conditions, that --quiet is never
prompted, and that autoindex never waits on stdin for a question it did not
print
. A help-text test pins all three strings.

Gates (re-run on the fixed tree)

  • cargo fmt --all -- --check — clean
  • cargo clippy -p xerj-autoindex --all-targets -- -D warnings — clean
  • cargo test -p xerj-autoindex475 passed / 0 failed (473 before; +2 new)

@cla-bot cla-bot Bot added the cla-signed label Aug 10, 2026
@xerj-org
xerj-org force-pushed the feat/ux-estimate-gate branch from 3955ccd to 99e8b3e Compare August 10, 2026 15:34
…n, index what matters first

`landing/llms.txt` already told an agent driving XERJ on someone's laptop to
estimate the job, tell the person, ask if it is big, and report progress. That
was advice, and advice is ignorable. This makes the binary do it.

MOTIVATION (owner, verbatim): "search should be smart with ai agent harness
getting real important things to index first and make it clear for user that
indexing will take time if many files, so xerj autoindex if estimated more than
10min work needs to ask AI back what to do".

--- (a) estimate, measured here, never on our box -------------------------

Phase A already reads and parses every file to sniff and sample it, so it now
TIMES that work per format family and prints a range with its basis:

  estimate: at least 64.2 s–64.2 s — a MEASURED FLOOR for client-side
            extraction, not a prediction of the whole run …
    estimate: code  500 files 749.3 MB at 11.7 MB/s measured over 500 file(s) → 64.2 s
    estimate: yaml   12 files  23.8 KB at 13.1 MB/s measured over  12 file(s) → 0.0 s

New `estimate` module. A family is priced ONLY from files phase A provably read
end to end — `exact_scan_bytes` decides that from the extractor's own contract
(whole-file parsers always; streaming parsers only under the sampling byte cap
AND short of the record cap; never sqlite, never gzip, since neither pair of
(size, elapsed) describes real throughput). Everything else is listed under
`unmeasured_families` with its bytes rather than priced at some other family's
rate, and `coverage` states the share of planned bytes the number covers. No
measurement → no number, no gate, and `basis` says why. The two ends are the
classical list-scheduling bounds (Graham 1969) over the phase-B worker count

It is a FLOOR and is labelled as one on every surface (`kind`,
`Estimate::headline`, `excludes`, the help, llms.txt). It covers client-side
extraction only: measuring the server, the network or embedding would mean
writing to the index the estimate exists to ask permission for. Measured:
68 MB source tree → floor 0.1 s vs real 8.9 s; 793 MB → 64.2 s vs ~350 s. So
the gate under-asks and never over-asks, and the "not triggered" line says in
words that silence is not a promise the run is short. A multiplier would have
closed the gap with a number nobody measured.

--- (b) the 10-minute gate ------------------------------------------------

New `gate` module. Past `--max-minutes` (default 10) with no answer, autoindex
indexes nothing, writes a JSON decision request to stdout and exits 4 — its own
code, because 1 is already the catch-all for every real failure and an agent
must be able to tell "choose something" from "your endpoint is down".

The payload carries the estimate and its basis, file/byte counts, the per-band
work order, the heaviest directories with real byte counts (flagged when they
match the vendored/generated rule), and four options. `narrower` is re-costed
with the same measured rates. `fast` deliberately states NO speed-up factor: it
reports which datasets and how many files it changes and says the factor was
not measured by this run — inventing one would break the honest-claims rule.

Answer with `--approve proceed|fast|cancel` (`--yes` = proceed);
`--max-minutes 0` disables it. `--approve fast` really applies
`--no-semantic --no-graph`; `--approve narrower` is REFUSED with instructions
rather than accepted and silently ignored (#204). A person at a terminal gets
the same facts as prose plus a prompt; a piped or agent-driven run never
touches stdin, and an unanswerable prompt is a cancel, never consent.

The gate sits before the first remote mutation — no index created, no mapping
upgraded, no plan persisted, no document written.

--- (c) index what matters first ------------------------------------------

New `order` module. The queue was sorted by size alone: right about scheduling,
silent about value, so a user who stopped early got whatever was largest —
on a source tree, `node_modules`. Work now drains by value band (source and
documents → configuration → structured data → logs and line files → vendored,
generated and minified), with the old biggest-first rule kept INSIDE each band
so a large file runs alongside its band instead of becoming the tail; a
single-worker run goes smallest-first, where there is no tail to hide in. One
exception: a file whose extraction outlasts everything ranked above it
(size × workers > the rest) starts first regardless of band. Bands, counts and
the reason each sits where it does are printed with the plan and included as
`priority_order` — an unexplained order is indistinguishable from an arbitrary
one. Bytes-based progress is unaffected; reordering does not move the
denominator.

Prior art (retrieval server on :9200 was down; corpus clones read directly):
tantivy log_merge_policy.rs:94-99 (MIT) sorts Reverse(size) then buckets into
levels — adapted, our levels are value bands; quickwit tool.rs:977-1012 and
ClickHouse ProgressIndication.cpp:118-119,223-227 both report measured bytes/s
and stop short of an ETA; meilisearch milli::progress reports steps and no ETA
at all. None estimates a job before running it.

--- verified live (branch binary, real node) ------------------------------

* 1.1 GB / 747 files, --workers 1 --max-minutes 1 → exit 4, JSON payload on
  stdout, and 0 `gt-*` indices and 0 brain-edge indices created.
* same run with --approve fast → gate bypassed, completes exit 0, `body`
  mapped `text` not `semantic_text`, no edges index written.
* --approve cancel → exit 0, reason=cancelled, nothing indexed.
* --dry-run --max-minutes 1 → reports "a real run would STOP here and exit 4"
  and does not stop; stdout is still exactly the plan JSON.
* ordering, 28 MB mixed corpus at 2 workers: first second of phase B is on
  src/mod_*.rs; the 1.7 MB node_modules/** files — the largest in the corpus,
  and therefore FIRST under the old rule — drain last.
* usage: --approve narrower/later, --max-minutes 10081/soon, and the flags on
  `map`/`status` all exit 2 with an explanation.

Gates: cargo fmt --all --check clean; cargo clippy -p xerj-autoindex
-p xerj-server --all-targets -D warnings clean; cargo test -p xerj-autoindex
390 passed / 0 failed; cargo test -p xerj-server --bins 30 passed / 0 failed;
ES-YAML 1366 passed / 0 failed / 3 skipped.

`xerj brain` composes autoindex and has no --approve of its own, so it sets
max_minutes: 0 explicitly — arming a gate there would stop the run with
instructions the caller cannot follow.
@xerj-org
xerj-org force-pushed the feat/ux-estimate-gate branch 2 times, most recently from 95408c6 to 91c1f9c Compare August 10, 2026 17:25
The gate's design is "never block on stdin", and it held everywhere except
one hole, found by measurement during PR #281's rebase verification:
`--quiet` / `--progress none` made the question INVISIBLE but still BLOCKING.

Root cause: the gate's prose and its prompt echo both go through the progress
surface (`Progress::note`), which `Surface::Silent` drops on the floor. But
`can_prompt()` tested only that stdin and stderr were TTYs — never that the
question had actually been printed. So a person at a terminal who passed
`--quiet` was asked nothing and then waited on `read_line` forever.

Measured on the pre-fix binary (370 MB / 540 files, `--workers 1
--max-minutes 1`, real pty, real node):

  --progress none   STILL RUNNING at 220 s, killed, **0 bytes ever printed**
  --quiet           STILL RUNNING at 220 s, killed, **0 bytes ever printed**
  normal TTY        prompt shown, answered 'c' → exit 0 "cancelled", 113.9 s
  stdin closed      exit 4 with the payload on stdout, 113.9 s

Same four after the fix:

  --progress none   exit 4 at 93.6 s, 4022 B of decision-request JSON on stdout
  --quiet           exit 4 at 93.9 s, same payload
  normal TTY        prompt shown, answered 'c' → exit 0 "cancelled", 93.7 s
  stdin closed      exit 4, 94.0 s
  --quiet --approve cancel   exit 0, 0 bytes (the documented way out, silence kept)
  --progress json TTY, 'f'   prompt still offered, answer honoured, exit 4

No `ax-*` index was created by any run: the gate still sits before the first
remote mutation.

Route (a), not (b) — the question is skipped rather than re-routed to stderr:

* `--quiet` has a documented contract in --help ("no progress and NO terminal
  line, only a fatal error: line"). Route (b) would have written ~30 lines of
  prose plus an interactive prompt to a stream the user explicitly silenced.
  Accepting a flag and then not honouring it is the accepted-and-ignored class
  tracked in #204 — the same defect this PR refuses elsewhere with
  `--approve narrower`.
* Route (a) is not a silent early exit. The decision request goes to **stdout**,
  which `--quiet` never claimed to silence ("stdout is the RESULT, stderr is
  PROGRESS"). The user still gets every fact, in machine-readable form, plus
  exit 4. Only the prose rendering is lost — and it was already opted out of.
* It makes quiet runs *uniform* with agent-driven ones: one rule to document,
  one rule to reason about, instead of a fourth distinct behaviour.
* Route (b) would also newly let a `--quiet` run block on stdin, enlarging the
  set of configurations that can wait for a human. That is the hazard itself.

Because a silent skip still owes the user an explanation, the payload now
carries `prompt_offered` and `prompt_not_offered_because`, and the prose
prints the same sentence wherever a surface exists to print it. Every reason
names the way out (`--approve`), so no path ends in "wait".

Implementation:

* `gate::NoPrompt` + `gate::prompt_blocked_by(stdin_tty, stderr_tty,
  question_visible)` — pure, so the whole rule is testable without a terminal,
  the same shape as `progress::resolve`. `question_visible` is the third
  condition that was missing.
* `gate::answer_from_terminal(blocked, open_stdin, echo)` takes stdin as a
  **closure**, so on a blocked path the process's stdin is never even opened.
  That is the invariant, not merely "no answer was returned".
* `can_prompt()` is removed rather than kept: a function with that name that
  ignores the surface is exactly the footgun that caused this.
* Call site in `lib.rs` decides once and uses it for both halves.

Regression test `gate::tests::a_question_that_cannot_be_seen_is_never_asked`
hands in a reader that panics if anyone touches it. Verified to fail without
the fix, on both halves of it:
  - classifier reverted → fails on the `QuestionSilenced` assertion
  - guard reverted      → panics "stdin was read for a question the user was
                          never shown" (gate.rs:800)

Docs: `--help` (`--quiet` and the ESTIMATE + DECISION GATE section) and
landing/llms.txt now state the three conditions, that `--quiet` is never
prompted, and that autoindex never waits on stdin for a question it did not
print.

Gates: cargo fmt --all -- --check clean; cargo clippy -p xerj-autoindex
--all-targets -- -D warnings clean; cargo test -p xerj-autoindex 475 passed /
0 failed (473 before; +2 new).
Conflicts: CHANGELOG.md (kept both release-notes sections) and
engine/crates/xerj-autoindex/src/cli.rs (main's #276 ignore-rules flags +
help section merged alongside this branch's estimate-gate help). The rerere
replay of the cli.rs resolution left a stray semicolon that turned
help_text() -> String into a unit return; removed it. Verified with scoped
cargo check -p xerj-autoindex (lib + tests) and cargo fmt --check.
@xerj-org
xerj-org merged commit fe5148f into main Aug 11, 2026
12 checks passed
xerj-org added a commit that referenced this pull request Aug 12, 2026
…build on release staleness (#298)

Issue #298 pins ROADMAP.md as the authoritative "where the project is
going" document and advertises that parts of it are machine-checked. At
HEAD (31d1fd5, the `release: cut 1.0.0-rc.16` commit) the file had
drifted from every promise the issue makes:

- "Last reviewed: … against v1.0.0-rc.15" while CHANGELOG.md already
  records 1.0.0-rc.16 as cut.
- "## Next release — v1.0.0-rc.16" described the release that had just
  shipped: five PRs listed "in flight" (#290 #281 #261 #296 #297) had
  merged; #258 alone remains open.
- Closed issues listed as open roadmap items: #293 (fixed by #304),
  and GA gate entries #270 (PR #300), #269 (PR #308), #207 (PR #261),
  plus the closed #204 umbrella.
- The ES-YAML count read 1,365/1,368 against a measured 1,366/1,369.

All 13 existing docs_capability_lists tests were green the whole time:
they pin capability counts, but nothing tied the roadmap to the release
history, so a cut that forgets to roll the roadmap forward passed CI.

Fix, in the same shape as the #211 capability-list guard:

- Two new tests in docs_capability_lists.rs pin ROADMAP.md to
  CHANGELOG.md's latest `## [<version>]` heading:
  `the_roadmap_next_release_has_not_already_been_cut` (the next-release
  heading must name a version strictly newer than the latest cut) and
  `the_roadmap_review_line_names_the_latest_cut_release` (the review
  line must name the latest cut release). Both FAIL before this
  ROADMAP.md refresh (verified: 13 passed / 2 failed at HEAD) and pass
  after it. Cutting rc.18 without re-reviewing ROADMAP.md now fails the
  build.
- ROADMAP.md re-reviewed against v1.0.0-rc.16 and main: rc.16 recorded
  as cut with the roadmap items it retired; next-release section now
  points at the v1.0.0-rc.17 milestone with the real open set (#294
  carried over; residuals #310 #311 #312; PRs #258 #313 #274 in
  flight); GA gate list drops the shipped items (#270 #269 #207) and
  reflects the closed #204 umbrella; conformance count updated.
- The same stale 1,365/1,368 count fixed in README.md (badge + prose),
  landing/llms.txt and landing/llms-full.txt. The fresh number traces
  to a verified run: CI job 93861837729 on 31d1fd5 — "1366 passed ·
  0 failed · 3 skipped · 1369 total".

GitHub-side, per the issue's "every open issue is triaged onto a
milestone": milestone v1.0.0-rc.17 created, #294/#310/#311/#312 moved
onto it, milestone v1.0.0-rc.16 closed.

Gate: cargo fmt --check clean, clippy clean, docs_capability_lists
15 passed / 0 failed. No engine runtime code is touched (the new file
is test-only), so the ES-YAML suite is unchanged from the green run
above; PR CI re-runs it.

Files:
- engine/crates/xerj-engine/tests/docs_capability_lists.rs (release
  freshness section, end of file)
- ROADMAP.md, README.md, landing/llms.txt, landing/llms-full.txt
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant