41043d686b
ADR-0024 audited as fully implemented. Amend the ADR with a "Phase F minimal" implementation note (parser.rs retained as the router + ParseError home) and update the README index line to match. Reconcile docs/requirements.md against handoffs 10-14: refresh the test baseline (449 -> 1006), mark U4 (replay) satisfied, correct the A1 / H1a / H3 progress notes. Amend handoff-14: §3 flagged items both resolved (ranker kept, CommandNode.hint_mode removed); §4 rewritten as a concrete next-work pointer at the reconciled requirements.md.
285 lines
12 KiB
Markdown
285 lines
12 KiB
Markdown
# Session handoff — 2026-05-15 (14)
|
||
|
||
Fourteenth handover. This session cleared the remaining
|
||
handoff-12 backlog: every §2.1 carry-forward and the §2.2
|
||
deferred items. Eight focused commits, each a deliberate
|
||
decision the user signed off on.
|
||
|
||
**Headline: the handoff-12 §2 catalogue is now empty of
|
||
actionable items, and ADR-0024 is confirmed fully
|
||
implemented.** The two items §3 originally flagged are both
|
||
resolved. The next session's work is the product roadmap in
|
||
`docs/requirements.md` (reconciled this session) — see §4.
|
||
|
||
## State at handoff
|
||
|
||
**Branch:** `main`. Working tree clean. `origin/main` is at
|
||
`42cf851` (handoff-13); local HEAD is **8 commits ahead** — this
|
||
session's work, unpushed (the user pushes asynchronously).
|
||
|
||
Commits since handoff-13:
|
||
|
||
```
|
||
50b7825 Remove dead parse.token.* catalog entries
|
||
bcc5ad2 Matrix: pin natural candidate ordering
|
||
f1ff597 Hint: pedagogical Form-A pointer at Form B's first value slot
|
||
911a537 Walker: node-attached HintMode via Node::Hinted
|
||
9bbb96e Walker: memoize DynamicSubgrammar resolution to bound the Box::leak
|
||
90e3f5d Insert grammar: Form C type-awareness via lookahead
|
||
f46606b Runtime: schema-aware replay parsing
|
||
03dd900 Help: consume CommandNode.help_id — REGISTRY-driven in-app help
|
||
```
|
||
|
||
**Tests:** **1006 passing, 0 failing, 1 ignored** (up from 989).
|
||
The ignored test is the long-standing `` ```ignore `` doc-test
|
||
in `src/friendly/mod.rs`.
|
||
|
||
**Clippy:** clean with `nursery` lints + `-D warnings`.
|
||
|
||
## §1. What shipped — handoff-12 backlog cleared
|
||
|
||
### Dead `parse.token.*` catalog entries removed (50b7825)
|
||
|
||
The 5 structural-class + 3 lex-error entries handoff-12 §2.1
|
||
listed as unreachable are gone (catalog YAML + `keys.rs`).
|
||
|
||
### Ranker / natural candidate ordering (bcc5ad2)
|
||
|
||
The user's actual ranker need — `to` before `table` so
|
||
`add column to table T` reads in order; keywords before schema
|
||
identifiers — **already worked** via declaration-order
|
||
preservation + keywords-first sectioning in
|
||
`candidates_at_cursor`. Nothing pinned it; 8 matrix tests in
|
||
`tests/typing_surface/candidate_ordering.rs` now do. See §3 for
|
||
the `Ranker` *type* itself.
|
||
|
||
### serial/shortid pedagogical Form-A hint (f1ff597)
|
||
|
||
handoff-12 §2.2: at the first value slot of `insert into T
|
||
values (…)` for a table with auto-generated columns, the hint
|
||
now appends "(`id` auto-generated — skipped here; list columns
|
||
explicitly … to set it)". `hint_resolution_at_input` derives
|
||
the skipped columns from the post-walk `WalkContext` (Form B =
|
||
no `user_listed_columns` + table has serial/shortid columns);
|
||
the note fires only at the first slot. New
|
||
`HintResolution::form_b_autogen_skipped`, catalog key
|
||
`hint.value_slot_autogen_skipped`.
|
||
|
||
### Node-attached HintMode (911a537)
|
||
|
||
handoff-12 §2.1: the hint resolver's signature-matching (does
|
||
the expected set contain all five literal forms? an
|
||
`Ident{NewName}`?) is replaced by a grammar-declared
|
||
annotation. New `Node::Hinted { mode, inner }` wrapper; the
|
||
walker records the mode in `WalkContext::pending_hint_mode` on
|
||
entry and clears it on **any successful match** (the cursor
|
||
moved past the slot — this also undoes the leak where a failed
|
||
`Hinted` branch of a `Choice` would strand a stale mode). The
|
||
resolver reads `pending_hint_mode` directly.
|
||
|
||
Mechanism note: handoff-12 sketched threading `HintMode` through
|
||
the `Expectation` enum. ADR-0024 §HintMode only says "nodes
|
||
carry `HintMode`, the walker propagates it" — mechanism-
|
||
agnostic. The `WalkContext::pending_hint_mode` route (mirroring
|
||
the existing `pending_value_type`) was chosen as lower-risk; the
|
||
user was told and did not object.
|
||
|
||
### DynamicSubgrammar memoization (9bbb96e)
|
||
|
||
handoff-12 §2.1's `Box::leak`-per-walk. The handoff's arena
|
||
sketch was unworkable (it needs a lifetime-generic `Node` — a
|
||
major refactor). Instead `resolve_dynamic` memoizes factory
|
||
output on the schema state the factory reads (keyed by factory
|
||
fn-pointer + ctx fields). Each distinct value-list shape leaks
|
||
**once** — total leak bounded by distinct (schema × form)
|
||
combinations, not keystroke count. `TableColumn` gained `Hash`.
|
||
|
||
### Form C type-awareness (90e3f5d)
|
||
|
||
handoff-12 §2.2. Form C (`insert into T (vals)`) shared the `(`
|
||
opener with Form A, so its values weren't typed. The
|
||
explicit-`Choice`-branch split is impossible (committed-choice
|
||
semantics commit after `(` matches), so a new
|
||
`Node::Lookahead(fn(&WalkContext, &str, usize) -> Node)`
|
||
variant peeks the source: a value-literal first token routes
|
||
the paren through the typed `column_value_list` (Form B
|
||
dispatch contract); an identifier or empty paren routes to a
|
||
Form A column-name list. Form C values are now type- and
|
||
count-checked at parse time. `insert into T (` cleanly shows
|
||
Form A column candidates instead of mixed Form-A/C suggestions.
|
||
|
||
### Schema-aware replay (f46606b)
|
||
|
||
handoff-12 §2.1: `run_replay` parsed schemalessly. It now
|
||
re-snapshots the schema per line (extracted `build_schema_cache`,
|
||
shared with the interactive path) and parses with
|
||
`parse_command_with_schema` — typed-slot rejections fire at
|
||
replay parse time, matching interactive. New integration test
|
||
`replay_rejects_typed_slot_violation_at_parse_time`.
|
||
|
||
### help_id consumption (03dd900)
|
||
|
||
handoff-12 §2.1: every `CommandNode` declared an unused
|
||
`help_id`. `note_help` now iterates the command `REGISTRY` and
|
||
translates each `help_id` — a new command appears in `help`
|
||
automatically. 20 per-command catalog entries + 3 framing
|
||
entries; `help.in_app_body` removed. `CommandNode.help_id` lost
|
||
its `#[allow(dead_code)]`.
|
||
|
||
## §2. Bug found this session
|
||
|
||
**`libyml` 0.0.5 scanner panic on long space runs in
|
||
double-quoted YAML scalars.** While authoring the help entries,
|
||
a space-aligned double-quoted catalog string
|
||
(`"quit — exit"`) panicked the YAML scanner with
|
||
"String join would overflow memory bounds". Block scalars
|
||
(`|-`) are unaffected — that's why the old block-scalar help
|
||
worked. Bisected and worked around: all per-command help
|
||
entries use `|-`. **If you author new catalog entries, avoid
|
||
long internal space runs in double-quoted (`"…"`) values — use
|
||
a block scalar or keep runs short.** A catalog comment in
|
||
`en-US.yaml` records this.
|
||
|
||
## §3. Flagged items — both now resolved
|
||
|
||
Both items this section originally flagged were ruled on by
|
||
the user after the main work:
|
||
|
||
**3.1 The `Ranker` type — KEEP.** `completion::Ranker` /
|
||
`candidates_at_cursor_with` have no production caller passing a
|
||
non-identity ranker (the candidate-ordering need is met by
|
||
declaration-order preservation). The user ruled: **keep it** —
|
||
it is intentional scaffolding for future frequency / content-
|
||
aware ranking (ADR-0024 §"out of scope" explicitly anticipates
|
||
this). No longer an open question.
|
||
|
||
**3.2 `CommandNode.hint_mode` — REMOVED.** The per-command
|
||
`hint_mode` field predated the node-attached HintMode work and
|
||
was read by nothing. Removed (field + 20 `None` initialisers)
|
||
in commit `6d2b929`.
|
||
|
||
## §4. What's next — the standing roadmap
|
||
|
||
**handoff-12 §2's backlog is cleared, and ADR-0024 is
|
||
confirmed fully implemented** (audited this session — Phases
|
||
A–F done; Phase F shipped "minimal" with `parser.rs` retained
|
||
as the router, now recorded in an ADR-0024 implementation
|
||
note). There is no migration or carry-forward debt left.
|
||
|
||
**The next session's work is the product roadmap, tracked in
|
||
`docs/requirements.md`** — reconciled this session against
|
||
what handoffs 10–14 actually built (test baseline refreshed to
|
||
1006; `U4` replay marked satisfied; `A1` / `H1a` / `H3`
|
||
progress notes corrected). `requirements.md` is now the
|
||
trustworthy "what's open" tracker — read it, not the
|
||
(coarser) `CLAUDE.md` "Things deliberately deferred" list.
|
||
|
||
Notable open clusters in `requirements.md` (prioritisation is
|
||
a **user product decision** — do not pick unilaterally):
|
||
|
||
- **Indexes** (`C3` partial) — `add index` / `drop index`,
|
||
then `EXPLAIN QUERY PLAN` rendering (`QA1`). Self-contained.
|
||
- **Complex WHERE expressions** (`C5a`, `[~]`) — AND/OR /
|
||
comparison / LIKE in UPDATE/DELETE/show-data filters. Needs
|
||
an ADR. The bridge from DSL toward real SQL.
|
||
- **SQL in advanced mode** (`Q1`/`Q4`, `[~]`) — `sqlparser-rs`
|
||
+ a defined subset. Needs an ADR.
|
||
- **Snapshot / undo** (`U1`/`U2`) — designed in ADR-0006, not
|
||
built. (`replay`, `U4`, is now done.)
|
||
- **m:n convenience** (`C4`), **modify relationship** (`C3a`,
|
||
`[~]`), **table rename** (`C1`).
|
||
- **Friendly error layer** (`H1`) — partial; full SQL→English
|
||
translation pending. **Syntax-help in parse errors**
|
||
(`H1a`) — piecemeal so far.
|
||
- **Session log + Markdown export** (`V4`, `[~]`),
|
||
**multi-line input** (`I1`), **readline shortcuts**
|
||
(`I1b`), **seeding** (`SD1`), **CI** (`TT5`),
|
||
**tutorial system** (`TU1`, `[~]`).
|
||
|
||
Two handoff-13 items the user already **accepted** (not work,
|
||
just recorded):
|
||
|
||
- **Partial entry words classify as `DefiniteErrorAt`**
|
||
(handoff-13 §3) — documented by the matrix test
|
||
`app_commands::partial_entry_word_classifies_as_definite_error_but_completes`.
|
||
- **Matrix scope** (handoff-13 §4) — cursor coverage is
|
||
"meaningful transitions" not every byte offset; assertion (5)
|
||
is parse-layer, not a live dispatch differential.
|
||
|
||
## §5. Architectural delta (vs. handoff-13)
|
||
|
||
### New `Node` variants
|
||
|
||
- `Node::Hinted { mode: HintMode, inner: &'static Node }` —
|
||
node-attached hint-mode annotation.
|
||
- `Node::Lookahead(fn(&WalkContext, &str, usize) -> Node)` —
|
||
source-aware dynamic subgrammar (Form A/C discrimination).
|
||
|
||
### New `WalkContext` field
|
||
|
||
- `pending_hint_mode: Option<HintMode>` — set on `Hinted`
|
||
entry, cleared on any match.
|
||
|
||
### Walker driver
|
||
|
||
- `walk_node` split into a wrapper (clears `pending_hint_mode`
|
||
on match) + `walk_node_inner` (the dispatch).
|
||
- `resolve_dynamic` + `DYNAMIC_CACHE` — memoized
|
||
`DynamicSubgrammar` resolution.
|
||
- `Node::Lookahead` arm — not memoized (source-dependent),
|
||
returns a small node.
|
||
|
||
### New API surface
|
||
|
||
- `input_render::classify_input_with_schema` (added handoff-13,
|
||
noted here for completeness).
|
||
- `HintResolution::form_b_autogen_skipped: Vec<String>`.
|
||
- `runtime::build_schema_cache` (extracted from
|
||
`refresh_schema_cache`).
|
||
|
||
### Catalog
|
||
|
||
- Removed: `parse.token.*` (×8), `help.in_app_body`.
|
||
- Added: `hint.value_slot_autogen_skipped`,
|
||
`parse.custom.insert_form_a_missing_values` (handoff-13),
|
||
`help.intro` / `help.dsl_section` / `help.types_reference`,
|
||
`help.{app,ddl,data}.*` (×20).
|
||
|
||
### Post-handoff cleanup (this section's work)
|
||
|
||
After the eight items above, three follow-ups landed:
|
||
|
||
- `CommandNode.hint_mode` field removed (commit `6d2b929`) —
|
||
see §3.2.
|
||
- ADR-0024 amended with a "Phase F minimal" implementation
|
||
note (`parser.rs` retained as the router); `docs/adr/README.md`
|
||
index line updated to match.
|
||
- `docs/requirements.md` reconciled — see §4.
|
||
|
||
## §6. How to take over
|
||
|
||
1. **Read this file, then handoff-13, then 12** for the chain.
|
||
2. **Read `CLAUDE.md`** — the working-style rules. This session
|
||
escalated every ambiguous fork (HintMode mechanism, the
|
||
`Box::leak` arena's true cost, the Form C restructure twice)
|
||
rather than deciding unilaterally.
|
||
3. **Read `docs/requirements.md`** — reconciled this session;
|
||
it is the authoritative "what's open" tracker (§4).
|
||
4. **Run `cargo test`** — 1006 passing, 0 failing, 1 ignored.
|
||
5. **Run `cargo clippy --all-targets -- -D warnings`** — clean.
|
||
6. **Pick the next work from §4 / `requirements.md`** — but
|
||
prioritisation is a user product decision; ask, don't
|
||
assume. handoff-12's backlog and ADR-0024 are both fully
|
||
done — there is no carry-forward debt.
|
||
|
||
### Note on the typing-surface matrix
|
||
|
||
`tests/typing_surface/` (now 144 cells) is the regression net
|
||
for everything walker/hint/completion. After any grammar or
|
||
walker change: a failing matrix cell with *correct* new
|
||
behaviour → update its snapshot
|
||
(`INSTA_UPDATE=always cargo test --test typing_surface_matrix
|
||
<family>`); a failing cell with *wrong* behaviour → the cell
|
||
earned its keep. The Form C type-awareness work this session
|
||
was guarded entirely by it.
|