feat(hint): H2 Phase B — per-form keying + the three exemplars (ADR-0053)

The first exemplar (`add 1:n relationship`) showed per-node keying is
too coarse for multi-form commands, so revise the mechanism to per-form.

- CommandNode `hint_id: Option<&str>` -> `hint_ids: &[&str]` (mirrors
  usage_ids); hint_key_for_input_in_mode reuses a factored-out
  pick_form_key (shared digit/m:n/suffix form disambiguation with
  usage_key_for_input_in_mode)
- wire INSERT + ADD (all four forms) with hint_ids
- author the three approved exemplars: hint.cmd.insert,
  hint.cmd.add_relationship, hint.err.foreign_key.child_side
  (what/example/concept) + keys.rs registration
- revise ADR-0053 D3 to per-form; record clause-concept hints as a
  deferred extension (issue #37); update README + plan
- +5 tests; 2488 pass / 1 ignored, clippy clean
This commit is contained in:
claude@clouddev1
2026-06-15 12:18:41 +00:00
parent 050b36391e
commit 4a5fd1b5c1
11 changed files with 292 additions and 109 deletions
@@ -39,19 +39,29 @@ Build order: **Phase A** (mechanism skeleton, falls back to tier-2) →
**Phase C** (comprehensive content, batched) → **Phase D** (polish:
strip advertisement, snapshots, full green).
## 3. Grammar: the `hint_id` field + the `HINT` node
## 3. Grammar: the `hint_ids` field + the `HINT` node
### 3a. New `CommandNode.hint_id`
- Add `pub hint_id: Option<&'static str>` to `CommandNode`
(`src/dsl/grammar/mod.rs:512`, beside `help_id` / `usage_ids`), with a
doc comment mirroring `help_id`'s. Compiler will force every node
literal (~37, across `grammar/app.rs`, `data.rs`, `ddl.rs`) to set it —
in Phase A set them all to `None` (everything falls back to tier-2);
fill them in Phase C.
- **Why `hint_id` not `help_id`** (ADR-0053 D3): `help_id` is `None` on the
7 advanced-SQL forms purely to dedup the `help` *list*; those forms have
distinct SQL syntax and need their own block. `hint_id` is 1:1 with
forms.
### 3a. New `CommandNode.hint_ids` (per-form — revised in Phase B)
- Add `pub hint_ids: &'static [&'static str]` to `CommandNode`
(`src/dsl/grammar/mod.rs:512`, beside `help_id` / `usage_ids`),
**mirroring `usage_ids`***not* a per-node `Option<&str>`. The Phase-B
exemplar (`add 1:n relationship`) showed per-*node* keying is too coarse:
`add`/`drop`/`show`/`create` are each one node spanning many forms, and
a live-input hint must be specific to the typed form. Compiler forces
every node literal (~37, across `grammar/app.rs`, `data.rs`, `ddl.rs`) to
set it — Phase A/B leave most `&[]` (tier-2 fallback); Phase C fills them.
**Multi-form nodes list ALL their form keys** (e.g. `add`
`["add_column", "add_relationship", "add_index", "add_constraint"]`) so
the form-word disambiguation resolves correctly and unauthored forms fall
back at render rather than mis-resolving to a sibling.
- **Lookup:** `hint_key_for_input_in_mode(source, mode)` returns the single
typed form's hint stem, reusing `pick_form_key` (factored out of
`usage_key_for_input_in_mode` — shared digit/`m:n`/suffix disambiguation).
- **Why a new field, not `help_id`** (ADR-0053 D3): `help_id` is `None` on
the 7 advanced-SQL forms purely to dedup the `help` *list*; those forms
have distinct SQL syntax and need their own block. `hint_ids` is per
form. (The parallel `help`-side gap is issue #36; clause-concept hints
are deferred — issue #37.)
### 3b. `AppCommand::Hint` + the `HINT` node
- `AppCommand::Hint` variant (no fields — no topic arg) in