Skip to content

feat(interactive): the guided ? line carries a label, not just an answer (cli#504) - #518

Merged
LukasWodka merged 2 commits into
developfrom
feat/504-labelled-prompts
Aug 17, 2026
Merged

feat(interactive): the guided ? line carries a label, not just an answer (cli#504)#518
LukasWodka merged 2 commits into
developfrom
feat/504-labelled-prompts

Conversation

@LukasWodka

@LukasWodkaLukasWodka commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Closes part 1 of #504. Part 2 (answer recall + Tab completion) is deliberately not here — see Why this is split at the bottom.

The problem

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 from the command line, as ? [~/mydata]: a question mark, a bracket and a path, with no verb.

The bare mode's premise was sound (the CLI already prints the question as a Step 3 of 4 · Where is your data? header, so repeating it on the ? line would duplicate it). 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.

Before / after

One prompt line, as the user sees it (path step, with ~/mydata pre-filled from the command line):

 Step 3 of 4 · Where is your data?
Give the path to a file or a folder — whichever holds your data:
· Tabular one CSV file e.g. ~/data/patients.csv
· Images a folder with labels.csv + images/ e.g. ~/data/xray/
· Text a folder with labels.csv + texts/ e.g. ~/data/reviews/
- ? [~/mydata]
+ ? Path: [~/mydata]

And the accept-the-default case, which was the worst of them — a bare ? alone on the line:

 Column types
We infer each column's type from your CSV. Press Enter to accept, or type overrides like age:INT,price:FLOAT.
- ?
+ ? Column types:

The rule

The label is the shortest noun phrase that names the answer, with a trailing colon. The header asks the question; the label says what you are typing into.

HeaderLabel
Step 1 of 4 · Do you want to ingest training or test data?Split:
Step 2 of 4 · Please name the dataset.Name:
Step 3 of 4 · Where is your data?Path:
Step 4 of 4 · What kind of machine learning task is this data for?Task:
What kind of data is this?Data type:
Which column holds the label?Label:
Which column holds the value to predict?Target:
How many keypoints per sample?Keypoints:
Image resolutionResolution:
Column typesColumn types:
Label policyLabel policy:
Time columnTime column:

Three deliberate calls:

  • bare is deleted, not left unused. A field nobody sets is a rendering a future call site can still reach. With it gone, surveyPrompter has no way to blank the Message.
  • The confirm keeps the whole question (? Proceed with the ingest? (y/N)). It has no header of its own, and the overwrite-replace confirm fires during the cluster phase with nothing printed before it — ? Overwrite: (y/N) would name the noun and hide the stakes. This asymmetry is now asserted, not just commented.
  • Label: vs Target: stay distinct. Collapsing both to Label: would have been tidier and would have quietly cost TestRunInteractive_RegressionLabelWording its teeth.

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. The register follows the header, not the prompter.

Test churn

~110 scripted answers are keyed by prompt label, so the labels rekey them. The rekeying is mechanical and lands in three files, not the two the issue named:

Two assertions were rewritten rather than rekeyed, because rekeying them would have left a check that nothing can fail:

  • TestRunInteractive_PathPromptCopyIsFileOrFolder asserted the data ingest: accept a file or a folder + instruction clarity + path bugs (RFC-0002 phase 3) #181 "file or folder" copy by searching the prompt label. A short noun cannot carry that sentence, so the check would have passed forever. It now reads the printed step — where the copy actually lives — and asserts both that the new wording is present and the old folder-only wording is gone.
  • TestRunInteractive_MLMSkipsLabel matched strings.HasPrefix(l, "Which column holds"), a stem the two label-column spellings shared. Label: and Target: share no stem, so it now names both.

New guard

TestRunInteractive_EveryGuidedPromptCarriesAShortLabel drives the realrunInteractive across seven scenarios (one per family, plus the variants that unlock the extras: regression → label policy, time-to-event → time column, keypoints → count + resolution, self-supervised text → no label, ambiguous layout → the data-type question) and asserts a property of whatever the flow asks:

  • non-empty
  • ends in :
  • contains no ? (it is a label, not a question)
  • at most 16 runes

The confirm label is asserted to be the opposite — a whole question.

Two things it deliberately does not do:

  • It restates no list. Nothing is scripted by label; every answer is the prompt's own default, pre-filled from runDataIngestArgs. So the recorded labels are the flow's, not the test's, and a question added later is covered without editing the test.
  • It does not pass on absence. Zero recorded prompts is a t.Fatalf, not a green run — every property above is trivially true of an empty slice.

Each property is its own if, not a switch. A full question violates several at once; a switch reports only the first, which would leave the length and ? checks never exercised by any mutation and therefore unproven. (That is exactly what the first draft did, and the mutation run below is how it was caught.)

Mutation evidence

Each mutation applied to the real production code, test run, then restored.

#MutationResult
1pr.Input("Path:", …)pr.Input("", …) — the exact old bare-mode defect❌ all 7 scenarios: prompt label is empty — the ? line would have no verb
2pr.Select("Task:", …)pr.Select("Which task?", …)must end in ':'
3pr.Select("Split:", …) → the full 44-rune questionall three properties report: no colon, is a question, over the 16-rune budget
4pr.Confirm("Proceed with the ingest?", …)pr.Confirm("Proceed:", …)confirm label "Proceed:" must be the whole question
5regression branch label "Target:""Label:"TestRunInteractive_RegressionLabelWording: 3 assertions, incl. LabelColumn = "age" — the wrong-column defect the wording protects against
6path hint → the old folder-only copyTestRunInteractive_PathPromptCopyIsFileOrFolder: both directions (lost the new copy, restored the old)
7fakePrompter.answer stops recording into askedno prompts recorded — the guard would be vacuous (the fail-closed clause, proven to fire rather than assumed)

Mutation 3 is the one that mattered: under the original switch it reported only the colon failure, so the ? and length properties were passing without ever being reachable. Independent ifs fixed that, and the table above is the re-run.

Golden diff — every line accounted for

01-data-ingest.golden — every ? <answer> becomes ? <label> <answer>, matching how survey renders Message + space + answer. The one shape change is the accept-the-default line: ?? Column types:. The catalog's own description gains a sentence about the label. No copy is lost.

zz-all-strings.golden — 10 additions, 1 removal:

  • Added (each is a new user-facing string, and each is the prompt label itself): "Column types:", "Data type:", "Keypoints:", "Label policy:", "Name:", "Path:", "Resolution:", "Split:", "Task:", "Time column:".
  • Removed:"Which task?" — and it is the only removal. It was reachable by the AST harvester only as the pr.Select label, because unlike every other guided question it has no PromptStep/Section twin: the task step's header is "What kind of machine learning task is this data for?", which is still there (unchanged, line 352 of the golden). So the question is gone from the source, not hidden from the backstop.

Everything else in that file is untouched. Notably the other question strings — "Please name the dataset.", "Where is your data?", "What kind of data is this?", "Column types", "Image resolution", "Label policy", "Time column" — all stay, because each is also a header literal. That is why the removal count is 1 and not 8.

Known, pre-existing gap, not introduced here:"Label:" and "Target:" do not appear in the backstop. They are held in a local variable before reaching promptLabelColumn, and the harvester only collects string literals passed directly to a copy call — the same reason the questions "Which column holds the label?" / "…the value to predict?" and their two desc hints were already absent before this PR. Label: is visible in 01-data-ingest.golden (rendered as a screen), and both labels are covered by the new guard plus the regression-wording test. Widening the harvester to follow variables is a separate change; happy to file it if you want it.

Docs

STYLE.md said the prompt line is "answer-only (? train) … the prompter runs bare". This PR makes that false, so it is updated in the same PR (org standard): the spacing rule keeps its section, and a new Guided-prompt labels section carries the rule, the label table, the confirm exception, and a pointer to the test that enforces it rather than restating it as prose.

Verification

  • make check — green
  • make check-all — green (full CI set: vet, tests + coverage gate, errcheck, ineffassign, misspell, staticcheck, golangci-lint 0 issues, schema check, govulncheck No vulnerabilities found, file budget, deadcode, style guard, tool pins)
  • go test ./... — green

Why this is split

Part 2 of #504 (remember the previous run's answers as defaults; Tab completion for the path prompt via survey's Suggest) is materially bigger than this: it needs a new state file under the tracebloc config dir, wiring into tracebloc delete so it is wiped like the rest, and a widened prompter seam. Landing it here would bury a ~110-line mechanical rekey under a behaviour change. It follows as its own PR.

🤖 Generated with Claude Code


Note

Low Risk
CLI prompt UX and test/golden churn only; ingest logic and cluster behavior are unchanged.

Overview
Fixes cli#504: guided tb data ingest no longer renders a bare ? or ? [~/mydata] when defaults are pre-filled. surveyPrompter's bare mode is removed; survey's Message is always set. Step headers still carry the full question; the ? line uses short noun labels (Split:, Path:, Task:, Label: / Target:, etc.).

STYLE.md documents guided-prompt labels (confirm prompts stay full questions). Tests and copy-catalog goldens are rekeyed to label-based prompter keys; TestRunInteractive_EveryGuidedPromptCarriesAShortLabel drives the real flow and asserts non-empty labels ending in :, no ? in labels, and a length budget. Version 0.10.9.

Reviewed by Cursor Bugbot for commit 699b8f5. Bugbot is set up for automated code reviews on this repo. Configure here.

…nswer (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 <noreply@anthropic.com>
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 <noreply@anthropic.com>

@saadqbalsaadqbal left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Careful, well-tested PR 👍 The label is pure presentation — it becomes survey's Message and never touches answer capture (promptLabelColumn's resolution still keys on supplied/headers), so the stored values are unchanged; the golden diff bears that out (? Path: ~/data/patients — same answer, label prepended). Nice that bare is deleted rather than left dead, that Label:/Target: and the header wording stay in lockstep, and that the #504 guard derives labels from the real flow instead of restating them. Verified go test ./internal/cli/ green.

@LukasWodka
LukasWodka merged commit 9d1ef08 into developAug 17, 2026
26 checks passed
@LukasWodka
LukasWodka deleted the feat/504-labelled-prompts branch August 17, 2026 11:37
@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

/fr-pass

Advanced at @LukasWodka's explicit direction. Held in the automated pass for a verification gap (interactive/TTY, GPU-only, or journey-dependent while e2e journey is red — backend#2206); Lukas is accepting that gap for this card.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@LukasWodka@saadqbal