From 9d1ef08bba9b2f11c120d112234ee84d0cc35a5a Mon Sep 17 00:00:00 2001 From: lukasWuttke <54042461+LukasWodka@users.noreply.github.com> Date: Mon, 17 Aug 2026 13:37:03 +0200 Subject: [PATCH 1/5] feat(interactive): the guided `?` line carries a label, not just an answer (cli#504) (#518) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(interactive): the guided `?` line carries a label, not just an answer (cli#504) The guided flow built its prompter as `surveyPrompter{bare: true}`, which set survey's Message to "". The prompt line rendered as a lone `?` — and since #505 started pre-filling answers, as `? [~/mydata]`: a question mark, a bracket and a path, with no verb. The bare mode's premise was sound (the CLI already prints `Step 3 of 4 · Where is your data?`, so repeating it on the `?` line would duplicate it) but the conclusion was not — and the codebase already said so. `Confirm` refused to go bare because "a bare `? (y/N)` there would be a label-less destructive prompt"; that objection was never Confirm-specific. Each guided prompt now passes a short noun label: the shortest noun phrase that names the answer, with a trailing colon. `? Path: ~/mydata`, `? Task: tabular_classification`, `? Column types:`. The header still asks the question; the label says what you are typing into. The label-column question keeps its two wordings on both lines — `Label:` for a class, `Target:` for a numeric value — so the branches stay distinguishable on the prompt line too. `bare` is deleted rather than left unused, so no future call site can reach the label-less rendering. Flows with no step header of their own (client create, delete, resources set) are untouched: they still pass the whole question, which is right for them. Tests: the ~110 scripted answers keyed by prompt label are rekeyed across interactive_test.go, copy_catalog_test.go and task_scope_test.go (the issue's file list missed the third; path_examples_test.go turned out to key on nothing). Two assertions were rewritten rather than rekeyed, because rekeying would have made them vacuous: the #181 file-or-folder copy check now reads the PRINTED step (a short label cannot carry that sentence), and the MLM no-label-question check names both `Label:` and `Target:` instead of matching a shared "Which column holds" stem that no longer exists. New guard TestRunInteractive_EveryGuidedPromptCarriesAShortLabel drives the real flow across seven scenarios and asserts a property of whatever it asks — non-empty, ends in ':', carries no '?', within a 16-rune budget — with the confirm asserted to be the opposite (a whole question). Nothing is scripted by label, so there is no list agreeing with itself; zero recorded prompts is a failure, not a pass. Co-Authored-By: Claude Opus 5 * chore(release): bump VERSION to 0.10.9 (cli#504) version-bump-gate failed this PR: v0.10.8 is already released and the diff touches published paths (internal/*). The release train reads VERSION and cuts the tag from it — it never bumps for you, so leaving it stale does not fail here, it fails the next prod hop days later on somebody else (backend#1561). Co-Authored-By: Claude Opus 5 --------- Co-authored-by: Claude Opus 5 --- STYLE.md | 29 +- VERSION | 2 +- internal/cli/copy_catalog_test.go | 63 ++-- internal/cli/data_ingest_cmd.go | 8 +- internal/cli/interactive.go | 91 ++--- internal/cli/interactive_test.go | 331 +++++++++++++----- internal/cli/task_scope_test.go | 40 +-- .../cli/testdata/golden/01-data-ingest.golden | 37 +- .../cli/testdata/golden/zz-all-strings.golden | 11 +- 9 files changed, 406 insertions(+), 206 deletions(-) diff --git a/STYLE.md b/STYLE.md index fcbde6b..7ba0644 100644 --- a/STYLE.md +++ b/STYLE.md @@ -55,10 +55,31 @@ flow) use one uniform rhythm so every question reads the same: - **A result that belongs to an answer attaches to it with no blank** — e.g. the `✔ Found a CSV table …` sniff echo sits directly under the path answer. -So: `header → blank → [supporting text → blank] → ? prompt`. The prompt line is -answer-only (`? train`); the question lives in the header (the prompter runs -`bare`), never repeated on the `?` line. Keep it uniform — don't hand-tune the -spacing of individual questions. +So: `header → blank → [supporting text → blank] → ? prompt`. Keep it uniform — +don't hand-tune the spacing of individual questions. + +## Guided-prompt labels + +The `?` line carries a **short noun label**, not the question: `? Path: ~/mydata`, +`? Task: tabular_classification`. The question lives in the header and is never +repeated on the `?` line; the label says what you are typing into. + +- The label is the **shortest noun phrase that names the answer**, with a trailing + colon — `Split:`, `Name:`, `Path:`, `Task:`, `Data type:`, `Label:` / `Target:`, + `Keypoints:`, `Resolution:`, `Column types:`, `Label policy:`, `Time column:`. +- **A prompt never goes label-less.** The guided flow used to blank survey's + `Message` (a `bare` mode), which rendered a lone `?` — and once answers were + pre-filled, `? [~/mydata]`: a question mark, a bracket and a path, with no verb + (cli#504). `internal/cli/interactive_test.go` asserts the property — non-empty, + ends in `:`, no `?`, within a length budget — against whatever the real flow + asks, so a new question is covered without editing the test. +- **A confirm is the exception: it carries the whole question** (`? Proceed with + the ingest? (y/N)`). A y/N prompt has no header of its own, and the + overwrite-replace confirm fires with nothing printed before it — a noun there + would name the object and hide the stakes. +- Flows with no step headers of their own (`client create`, `delete`, + `resources set`) pass the whole question as the label. The register follows the + header, not the prompter. ## Terminology diff --git a/VERSION b/VERSION index 1a46c7f..f314d02 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.10.8 +0.10.9 diff --git a/internal/cli/copy_catalog_test.go b/internal/cli/copy_catalog_test.go index 5826d4e..cd6b50c 100644 --- a/internal/cli/copy_catalog_test.go +++ b/internal/cli/copy_catalog_test.go @@ -140,29 +140,29 @@ func TestCopyCatalog(t *testing.T) { imgDir := imageDirLayout(t) txtDir := textDirLayout(t) tabularIngest := driveIngest(tabDir, "~/data/patients", map[string]string{ - "Do you want to ingest training or test data?": "train", - "Please name the dataset.": "hospital_train", - "Where is your data?": tabDir, - "Which task?": "tabular_classification", - "Which column holds the label?": "churned", + "Split:": "train", + "Name:": "hospital_train", + "Path:": tabDir, + "Task:": "tabular_classification", + "Label:": "churned", }) imageIngest := driveIngest(imgDir, "~/data/xray", map[string]string{ - "Do you want to ingest training or test data?": "train", - "Please name the dataset.": "xray_train", - "Where is your data?": imgDir, - "Which task?": "image_classification", - "Which column holds the label?": "label", - "Image resolution": "224x224", + "Split:": "train", + "Name:": "xray_train", + "Path:": imgDir, + "Task:": "image_classification", + "Label:": "label", + "Resolution:": "224x224", }) // Text family: text_classification shows the label question; the picker lists // every text task + blurb. (Self-supervised text — masked/causal LM, seq2seq // — skips the label step; that path is covered by the backstop.) textIngest := driveIngest(txtDir, "~/data/reviews", map[string]string{ - "Do you want to ingest training or test data?": "train", - "Please name the dataset.": "reviews_train", - "Where is your data?": txtDir, - "Which task?": "text_classification", - "Which column holds the label?": "label", + "Split:": "train", + "Name:": "reviews_train", + "Path:": txtDir, + "Task:": "text_classification", + "Label:": "label", }) // execIngest renders the run that follows the confirm — the three steps and // the final summary. printLocalSummary + submit.RenderSummary are the REAL @@ -211,7 +211,7 @@ func TestCopyCatalog(t *testing.T) { } dataIngestFile := doc( "tb data ingest — stage a dataset into your secure environment", - "What you see when you run `tb data ingest` with no flags: a short intro, a\nfour-step guided setup (intent, name, path, task) then the task-specific\nquestions, and — after you confirm — the run itself. The setup is\ndriven through the real flow for one task in each family (tabular, image, text)\nso the task-specific questions are visible; each core question prints as a\n`Step N of 4 · …` header, the task-specific ones (the label column, and extras\nlike resolution or schema) as their own header, the\nsupporting line beneath it, and the `?` line shows your answer. The run (shown\nonce, for tabular) is the three steps + the final summary as the CLI renders\nthem. Values passed as flags (--as, --task, a path, …) pre-fill the matching\nquestions rather than skipping them — guided mode always asks. The\nother tasks' extra questions (keypoints, label policy, time column),\nself-supervised text (which skips the label question), and the failure-summary\nwordings are in zz-all-strings.golden. The raw ingestor stream the CLI streams\nthrough (MySQL waits, the 📊 banner, per-validator lines) is the engine's own\nstdout — not CLI copy — so it isn't shown. (`tb ingest` is a hidden deprecated\nalias; `push` is a deprecated alias of the verb.)", + "What you see when you run `tb data ingest` with no flags: a short intro, a\nfour-step guided setup (intent, name, path, task) then the task-specific\nquestions, and — after you confirm — the run itself. The setup is\ndriven through the real flow for one task in each family (tabular, image, text)\nso the task-specific questions are visible; each core question prints as a\n`Step N of 4 · …` header, the task-specific ones (the label column, and extras\nlike resolution or schema) as their own header, the\nsupporting line beneath it, and the `?` line carries a short noun label\n(`Path:`, `Task:`) plus your answer. The run (shown\nonce, for tabular) is the three steps + the final summary as the CLI renders\nthem. Values passed as flags (--as, --task, a path, …) pre-fill the matching\nquestions rather than skipping them — guided mode always asks. The\nother tasks' extra questions (keypoints, label policy, time column),\nself-supervised text (which skips the label question), and the failure-summary\nwordings are in zz-all-strings.golden. The raw ingestor stream the CLI streams\nthrough (MySQL waits, the 📊 banner, per-validator lines) is the engine's own\nstdout — not CLI copy — so it isn't shown. (`tb ingest` is a hidden deprecated\nalias; `push` is a deprecated alias of the verb.)", []run{ {"tb data ingest # guided · tabular classification", tabularIngest}, {"tb data ingest # guided · image classification", imageIngest}, @@ -476,8 +476,8 @@ func quoteAll(in []string) []string { return out } -// catalogPrompter is the prompter seam's catalog double: it prints each question -// the way the terminal shows it ("? ") and returns a scripted +// catalogPrompter is the prompter seam's catalog double: it prints each prompt +// the way the terminal shows it ("?