feat: H1a parse-error gaps G2–G4 + advanced near-miss matrix (ADR-0042)

Close the three remaining ADR-0042 triage gaps, each test-first, and
lock the advanced-mode near-miss matrix.

G2 — bare `select` dumped the 14-item expression first-set. Collapse
it to "a projection: `*`, a column, or an expression" in the error
message only (parser::format_walker_error), detected by the joint
`distinct`+`all` quantifier signature unique to a projection start.
Render-only: completion/hints still expand the full set (typing-surface
matrix unchanged).

G3 — the usage block was mode-blind: advanced `create table` showed the
DSL `create table … with pk …` template. usage_key(s)_for_input gain
mode-aware `_in_mode` variants selecting candidates by CommandCategory;
render_usage_block and the typing-time ambient usage thread the
submission mode. Advanced `create` now shows both SQL forms. A fallback
covers shared SQL nodes (insert/update/delete) that declare no
usage_ids of their own — without it they regressed to the
available-commands fallback (caught by the new advanced matrix).

G4 — `with` borrowed `select`'s usage template; give it its own
parse.usage.with CTE template.

Tests: new near_miss_matrix_advanced_mode (12 SQL-surface cases incl.
the available-commands regression guard) + per-gap tests; removed the
temporary baseline_dump. Full suite green (lib 1578 / it 386 /
typing_surface_matrix 192); clippy clean.
This commit is contained in:
claude@clouddev1
2026-06-05 14:57:20 +00:00
parent 10f8c2a95c
commit 649fdcb38e
8 changed files with 259 additions and 93 deletions
+8 -6
View File
@@ -1521,7 +1521,7 @@ impl App {
for note in notes {
self.note_error(note);
}
self.note_error(render_usage_block(input));
self.note_error(render_usage_block(input, mode));
return vec![Action::JournalFailure {
source: input.to_string(),
}];
@@ -1601,7 +1601,7 @@ impl App {
// known command-entry keyword was consumed) or
// the available-commands fallback (§5).
if let ParseError::Invalid { .. } = &err {
self.note_error(render_usage_block(input));
self.note_error(render_usage_block(input, mode));
}
// ADR-0034 §1/§2: a submitted line that failed to
// parse is journalled `err` so it is recallable
@@ -2557,16 +2557,18 @@ fn parse_error_message(err: &ParseError) -> String {
/// renders every catalog template — multi-form families like
/// `drop` show every variant. Otherwise the fallback lists every
/// entry keyword alphabetically.
fn render_usage_block(input: &str) -> String {
fn render_usage_block(input: &str, mode: Mode) -> String {
// A multi-form command that has committed to a form
// (`add index …`) shows only that form's usage; a bare
// multi-form entry word (`add`) shows the whole family.
// Mode-aware (ADR-0042 G3): in advanced mode a shared entry
// word shows its SQL forms, not the DSL templates.
let catalog_keys: Vec<&'static str> =
crate::dsl::grammar::usage_key_for_input(input)
crate::dsl::grammar::usage_key_for_input_in_mode(input, mode)
.map(|key| vec![key])
.or_else(|| {
crate::dsl::grammar::usage_keys_for_input(input)
.map(|(_word, all)| all.to_vec())
crate::dsl::grammar::usage_keys_for_input_in_mode(input, mode)
.map(|(_word, all)| all)
})
.unwrap_or_default();
if !catalog_keys.is_empty() {