ADR-0024 round-5 follow-up: surface tail-Optional expectations

Pre-Phase-D, `save ` parsed as a complete `save` command, so
the completion engine had nothing to mine: `as` never surfaced
as a Tab candidate. This is the round-5 gap the handoffs have
been tracking.

`WalkResult` gains a `tail_expected: Vec<Expectation>` field.
The walker's top-level `Matched` branch copies the outer
shape's skipped-Optional expectations into it. `expected_at_input`
returns `tail_expected` on `Match` so the completion engine
sees the optional-suffix continuations and offers them as Tab
candidates.

`hint_mode_at_input` deliberately does NOT consume
`tail_expected` — surfacing prose like "Type a name" at the
end of a valid command would be misleading. A new private
`expected_for_hint` returns empty on `Match` to preserve this.
The split distinguishes "valid + could continue" (completion
helps) from "invalid + must continue" (hint resolver helps).

Tests:
- `save ` Tab → `as` (new test, the original round-5 gap).
- `messages ` Tab → `short` and `verbose` (same shape).
- Existing `hint_mode_none_for_complete_command` stays green
  because hint resolver ignores tail_expected.
- 830 total passing, 0 failing, 1 ignored. Clippy clean.
This commit is contained in:
claude@clouddev1
2026-05-15 17:50:31 +00:00
parent abebd7944f
commit 8188fa5ee1
3 changed files with 103 additions and 16 deletions
+21 -8
View File
@@ -814,14 +814,27 @@ mod tests {
assert!(cs.contains(&"advanced".to_string()), "got {cs:?}");
}
// Note: `save ` and `messages ` are deliberately NOT tested
// here. Both commands accept their bare form as a valid parse
// `save` opens the save modal, `messages` shows the current
// verbosity — so the parser returns Ok at those positions
// and the completion engine has no expected-set to mine. The
// optional-suffix candidates (`as`, `short`, `verbose`) would
// need a separate probe mechanism (deferred — same shape as
// the post-complete-parse gap for `--create-fk` etc.).
// ---- Optional-suffix completion (round-5 gap, closed in Phase D) ----
//
// Pre-Phase-D: `save ` parsed as a valid `save` command, so
// the completion engine had no expected-set to mine and the
// `as` suffix never surfaced as a Tab candidate. Phase D's
// `WalkResult::tail_expected` carries the outer shape's
// skipped-Optional expectations even on `Match`, so these
// surface without a separate probe mechanism.
#[test]
fn save_space_offers_as_via_tail_expected() {
let cs = cands("save ", 5);
assert_eq!(cs, vec!["as".to_string()]);
}
#[test]
fn messages_space_offers_short_and_verbose_via_tail_expected() {
let cs = cands("messages ", 9);
assert!(cs.contains(&"short".to_string()), "got {cs:?}");
assert!(cs.contains(&"verbose".to_string()), "got {cs:?}");
}
// ---- Value-literal slot suppression (round-6) -----------