Skip to content

feat(setup): ask which package manager to use when the project is ambiguous - #784

Merged
ffantl-ld merged 14 commits into
fix/setup-rederive-on-overridefrom
feat/setup-pm-picker
Aug 20, 2026
Merged

feat(setup): ask which package manager to use when the project is ambiguous#784
ffantl-ld merged 14 commits into
fix/setup-rederive-on-overridefrom
feat/setup-pm-picker

Conversation

@ffantl-ld

@ffantl-ld ffantl-ld commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Requirements

  • I have added test coverage for new or changed functionality
  • I have followed the repository's pull request submission guidelines
  • I have validated my changes against all supported platform versions

Related issues

Step 4 of the package-manager selection design — the wizard step that actually asks. Stacked on #783, which supplies the confidence verdict and candidates.

Describe the solution you've provided

  • Add a package-manager step, reached only when the project does not state which manager it uses. A project that does goes straight to the plan, so the happy path gains no keystrokes.
  • The screen says why it is asking, using the reason from the verdict. Without that, stopping to ask reads as a tool that failed to look — and the reason is also what tells the user our reading of their project is wrong.
  • Each row shows the exact command that manager would run, applying the plan screen's promise one step earlier.
  • Installed managers are listed first and the cursor starts on one. An uninstalled manager stays selectable: the choice belongs to the user, and the install pre-flight already warns rather than installing tooling.
  • from the plan returns to the picker when it was shown, and to the SDK list when it was not.
  Which package manager should install the SDK?

  This project has lockfiles for more than one manager (yarn, npm).

    Select a package manager:

  │ npm
  │ npm install @launchdarkly/node-server-sdk

    yarn
    yarn add @launchdarkly/node-server-sdk

    pnpm
    pnpm add @launchdarkly/node-server-sdk

    bun (not installed)
    bun add @launchdarkly/node-server-sdk

Describe alternatives you've considered

  • Skipping the question when exactly one candidate is installed. Rejected: having pnpm installed says nothing about whether this repo uses it, and it would give two developers different answers for the same project.
  • Disabling rows for managers that are not installed. Rejected: the user may be about to install one, and the pre-flight check already stops with an install link if the tool is still missing at install time.
  • Putting the step before SDK confirmation. Rejected: an override changes the language, so a project that is definite as Node may be ambiguous as Python. The verdict has to be computed for the SDK the user actually chose.

Additional context

pmChoice being non-nil doubles as the record that the picker was shown, which is what back-navigation from the plan keys off.

Testing approaches

  • go test ./... passes.
  • A definite project (corepack pnpm@9.1.0) skips the picker entirely, leaves pmChoice nil, and plans pnpm add.
  • Conflicting lockfiles reach the picker, the reason names both managers, the rendered view contains it, and the plan afterwards runs the manager that was chosen.
  • Ordering and selectability: with npm uninstalled and yarn/pnpm installed, the installed two come first, the npm row is labelled (not installed), and selecting it still reaches the plan with npm chosen.
  • Back-navigation: plan → picker → SDK list.
  • Rendered both screens from a real model to check the wording and layout read correctly at 80x22.
  • Existing tests that relied on landing on the plan from a signal-less directory were updated: two now use a Gemfile project, one adds a package-lock.json, and the shared overrideToSDK helper walks through the picker when it appears, since those tests are about entry points rather than managers.

Note

Overview
When the chosen SDK’s project does not identify a single package manager, setup now stops after SDK selection and asks which manager should install the SDK. Definite projects still go straight to the plan.

The picker shows the verdict’s reason, lists the exact install command per row, puts installed tools first, and still lets the user pick an uninstalled one. Back from the plan returns to the picker when it was shown.

Also folds wizard key hints into the list help (instead of duplicating them in a footer), wraps plan steps and the wait-for-app copy so they fit the terminal, and covers skip/ask/back/layout in tests.

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

@ffantl-ld
ffantl-ld requested a review from a team as a code owner August 17, 2026 21:26
Comment thread cmd/setup/update.go Outdated
@ffantl-ld
ffantl-ld force-pushed the feat/setup-pm-confidence branch from 282f963 to 6df6b57 Compare August 18, 2026 15:22
@ffantl-ld
ffantl-ld force-pushed the feat/setup-pm-picker branch from 5e3b923 to dddaeee Compare August 18, 2026 15:22
@ffantl-ld
ffantl-ld force-pushed the feat/setup-pm-confidence branch from 6df6b57 to 3cfd103 Compare August 18, 2026 15:29
@ffantl-ld
ffantl-ld force-pushed the feat/setup-pm-picker branch 2 times, most recently from 9aad062 to 49139fb Compare August 18, 2026 15:54

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 1416177. Configure here.

Comment thread cmd/setup/view.go
@ffantl-ld
ffantl-ld force-pushed the feat/setup-pm-picker branch 2 times, most recently from 372500d to 98e4725 Compare August 18, 2026 18:00
@ffantl-ld
ffantl-ld force-pushed the feat/setup-pm-confidence branch from 055976e to 7fef87c Compare August 19, 2026 14:12
@ffantl-ld
ffantl-ld force-pushed the feat/setup-pm-picker branch from acce8bc to 96253c2 Compare August 19, 2026 14:12
@ffantl-ld
ffantl-ld force-pushed the feat/setup-pm-confidence branch from 7fef87c to 87e9ed0 Compare August 19, 2026 15:51
@ffantl-ld
ffantl-ld force-pushed the feat/setup-pm-picker branch from 4b3c992 to 90c681c Compare August 19, 2026 15:51
@ffantl-ld
ffantl-ld force-pushed the feat/setup-pm-confidence branch from 87e9ed0 to 9246e77 Compare August 19, 2026 16:09
@ffantl-ld
ffantl-ld force-pushed the feat/setup-pm-picker branch from 90c681c to df5ec57 Compare August 19, 2026 16:09
@ffantl-ld
ffantl-ld force-pushed the feat/setup-pm-confidence branch from 9246e77 to b4e5252 Compare August 19, 2026 16:52
@ffantl-ld
ffantl-ld force-pushed the feat/setup-pm-picker branch from df5ec57 to 94eebae Compare August 19, 2026 16:52
@ffantl-ld
ffantl-ld force-pushed the feat/setup-pm-confidence branch from b4e5252 to f0a249f Compare August 19, 2026 17:39
@ffantl-ld
ffantl-ld force-pushed the feat/setup-pm-picker branch from 94eebae to c3eb89b Compare August 19, 2026 17:39
@ffantl-ld
ffantl-ld force-pushed the feat/setup-pm-confidence branch from f0a249f to c10867f Compare August 19, 2026 18:18
@ffantl-ld
ffantl-ld force-pushed the feat/setup-pm-picker branch from c3eb89b to b0eb796 Compare August 19, 2026 18:18
@ffantl-ld
ffantl-ld force-pushed the feat/setup-pm-picker branch from b0eb796 to 6ece185 Compare August 19, 2026 18:40
@ffantl-ld
ffantl-ld force-pushed the feat/setup-pm-confidence branch from acfb88b to 6446556 Compare August 19, 2026 21:14
@ffantl-ld
ffantl-ld force-pushed the feat/setup-pm-picker branch from 6ece185 to e87936d Compare August 19, 2026 21:16
@ffantl-ld
ffantl-ld force-pushed the feat/setup-pm-confidence branch from 6446556 to d56b2d7 Compare August 20, 2026 15:13
@ffantl-ld
ffantl-ld force-pushed the feat/setup-pm-picker branch from e87936d to 768b8db Compare August 20, 2026 15:13
ffantl-ld and others added 2 commits August 20, 2026 13:17
…ntly

Detection always returned a manager, so a project that never said which one it
used got pip or npm presented as fact. Signals are now split into what the
project declares and what it merely implies, and a verdict is definite only when
the project names exactly one manager.

Recognise the corepack packageManager field, poetry.lock, Pipfile.lock and
[tool.pdm], which were previously read as pip or npm. [tool.hatch] is recorded as
a signal we cannot act on, since hatch has no dependency-add command.

`setup install` now reads the project when --package-manager is omitted, instead
of defaulting to pip or npm whatever the lockfiles say, and fails with the
candidate list when the project is ambiguous rather than picking for the user.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Real pyproject files rarely carry a bare [tool.x] header, so matching only that
left the poetry, uv, pdm and hatch signals near-dead: a hatch project configures
[tool.hatch.build], not [tool.hatch]. Nested tables now count, with the trailing
delimiter required so [tool.uv] does not match [tool.uvicorn].

A manager the project committed to now settles the verdict even when an
unactionable tool is also configured. hatchling is a common build backend for uv
and poetry projects, and uv can add the dependency whoever builds the wheel;
previously those projects were marked ambiguous and install refused to run.

Recognise pdm.lock, so a PDM project that commits only its lockfile is still
identified as one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ffantl-ld and others added 12 commits August 20, 2026 13:17
pnpm refuses to run at all when package.json names it without a version — "No
version specified for pnpm in packageManager" — so treating a versionless field
as the project's declared manager routed the user into a command that cannot
work. Such a field is no longer a declaration: the lockfiles decide, or the user
is asked.

When a manager still refuses for that reason, say so. The manifest is malformed
rather than the command wrong, and repairing someone's manifest is not ours to do.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Corepack accepts only an exact version, so a range is refused outright —
"Invalid package manager specification in package.json (pnpm@^11.13.0); expected
a semver version" — as is a missing version. Treating either as the project's
declared manager routed the user into a command that cannot run. Only an exact
MAJOR.MINOR.PATCH, optionally with prerelease or build metadata, now counts;
anything else leaves the lockfiles to decide or the user to be asked.

When a manager refuses for that reason, say which part is wrong. The manifest is
malformed rather than the command, and repairing someone's manifest is not ours
to do.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Matching [tool.<name>] as text counted comments and strings, so a uv project whose
comment mentioned the tool it migrated away from was marked ambiguous and install
refused to run. Reading the declared tables instead means only a declaration counts,
and nested tables need no special case: TOML creates the parent implicitly, so
[tool.hatch.build] alone still declares hatch.

A file we cannot parse declares nothing, which leaves the project ambiguous and the
user asked — the honest answer when we cannot read what manages it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Detection parsed the file twice for a Python project: once for the detector's own
package manager and again for the confidence verdict. The verdict is now the single
source for the languages it models, and the detector leaves the field to it. A
language it does not model, such as Java's maven versus gradle, keeps the answer its
detector gives.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The packageManager guidance matched a missing or non-semver version anywhere in an
install failure, so a gem, a Python package or a Go module reporting either phrase
had its real error replaced by advice about a file it does not have. The output has
to name package.json, which both corepack refusals do.

Say what was actually found when a project is set up for more than one manager: a
Pipfile and a [tool.*] table count as commitments too, so naming lockfiles sent the
reader looking for files that are not there.

Note on stderr when no --package-manager was given, since that used to fall through
to npm or pip and now reads the project. Callers parsing output are unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…iguous

A project with lockfiles for two managers, or none at all, has no answer we can
read off disk, and picking one is how a yarn project gets installed with npm. The
wizard now asks, and says why it is asking.

Installed managers are listed first and the cursor starts on one, but an
uninstalled manager stays selectable: the choice is the user's and the install
step already warns rather than installing tooling. Projects that state their
manager go straight to the plan.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The screen draws a title, the reason for asking and a key hint around the list,
but the list was sized to the whole window, so the hint — including how to go
back — was pushed off the bottom at every terminal size. The list now leaves room
for that chrome, and the list's own help line goes away since the screen prints
its own. Below fourteen rows the reason is dropped: at that size the question and
the choices matter more than the explanation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The project and environment screens printed a footer of key hints while the list
below already rendered its own help, so every instruction appeared twice. The
wizard's own bindings now go into the list's help line, which is the single place
a screen states them, and the footers are gone.

The package-manager picker follows the same shape, and its height reserve is
retuned for the help line the list now draws, including the extra row that line
takes when the terminal is narrower than it is.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A step naming an absolute entry-point path alongside the warning that no entry
file was found runs well past a narrow terminal, and overflowing there hides the
warning the step exists to give. Steps now wrap, with what wraps indented under
the number so a step still reads as one item. The screen that names the injected
file wraps for the same reason.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Wrapping pads every line to the full width, so the newline left inside the
wrapped lead put a whole row of spaces in front of the file path and carried it
past the edge of the terminal. The newline now sits outside the wrap, and the
closing instruction wraps too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The list inherits the same quit binding as the others, so esc arriving on its own
ended the session from the picker.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Verification cannot succeed until the user's application is running, so naming
only the SDK left it unclear whether anything was expected of them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ffantl-ld
ffantl-ld force-pushed the feat/setup-pm-confidence branch from d56b2d7 to 04662ac Compare August 20, 2026 17:18
@ffantl-ld
ffantl-ld force-pushed the feat/setup-pm-picker branch from 768b8db to 79be31b Compare August 20, 2026 17:18
Base automatically changed from feat/setup-pm-confidence to fix/setup-rederive-on-override August 20, 2026 17:31
@ffantl-ld
ffantl-ld merged commit 78c9dc9 into fix/setup-rederive-on-override Aug 20, 2026
6 checks passed
@ffantl-ld
ffantl-ld deleted the feat/setup-pm-picker branch August 20, 2026 17:32
ffantl-ld added a commit that referenced this pull request Aug 20, 2026
…iguous (#784)

* feat(setup): report package-manager confidence and stop guessing silently

Detection always returned a manager, so a project that never said which one it
used got pip or npm presented as fact. Signals are now split into what the
project declares and what it merely implies, and a verdict is definite only when
the project names exactly one manager.

Recognise the corepack packageManager field, poetry.lock, Pipfile.lock and
[tool.pdm], which were previously read as pip or npm. [tool.hatch] is recorded as
a signal we cannot act on, since hatch has no dependency-add command.

`setup install` now reads the project when --package-manager is omitted, instead
of defaulting to pip or npm whatever the lockfiles say, and fails with the
candidate list when the project is ambiguous rather than picking for the user.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(setup): match nested tool tables and let a committed manager win

Real pyproject files rarely carry a bare [tool.x] header, so matching only that
left the poetry, uv, pdm and hatch signals near-dead: a hatch project configures
[tool.hatch.build], not [tool.hatch]. Nested tables now count, with the trailing
delimiter required so [tool.uv] does not match [tool.uvicorn].

A manager the project committed to now settles the verdict even when an
unactionable tool is also configured. hatchling is a common build backend for uv
and poetry projects, and uv can add the dependency whoever builds the wheel;
previously those projects were marked ambiguous and install refused to run.

Recognise pdm.lock, so a PDM project that commits only its lockfile is still
identified as one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(setup): require a version in the packageManager field

pnpm refuses to run at all when package.json names it without a version — "No
version specified for pnpm in packageManager" — so treating a versionless field
as the project's declared manager routed the user into a command that cannot
work. Such a field is no longer a declaration: the lockfiles decide, or the user
is asked.

When a manager still refuses for that reason, say so. The manifest is malformed
rather than the command wrong, and repairing someone's manifest is not ours to do.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(setup): require one exact version in the packageManager field

Corepack accepts only an exact version, so a range is refused outright —
"Invalid package manager specification in package.json (pnpm@^11.13.0); expected
a semver version" — as is a missing version. Treating either as the project's
declared manager routed the user into a command that cannot run. Only an exact
MAJOR.MINOR.PATCH, optionally with prerelease or build metadata, now counts;
anything else leaves the lockfiles to decide or the user to be asked.

When a manager refuses for that reason, say which part is wrong. The manifest is
malformed rather than the command, and repairing someone's manifest is not ours
to do.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(setup): read pyproject.toml as TOML to find configured tools

Matching [tool.<name>] as text counted comments and strings, so a uv project whose
comment mentioned the tool it migrated away from was marked ambiguous and install
refused to run. Reading the declared tables instead means only a declaration counts,
and nested tables need no special case: TOML creates the parent implicitly, so
[tool.hatch.build] alone still declares hatch.

A file we cannot parse declares nothing, which leaves the project ambiguous and the
user asked — the honest answer when we cannot read what manages it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor(setup): read pyproject.toml once per detection

Detection parsed the file twice for a Python project: once for the detector's own
package manager and again for the confidence verdict. The verdict is now the single
source for the languages it models, and the detector leaves the field to it. A
language it does not model, such as Java's maven versus gradle, keeps the answer its
detector gives.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(setup): stop misreading other ecosystems' install errors

The packageManager guidance matched a missing or non-semver version anywhere in an
install failure, so a gem, a Python package or a Go module reporting either phrase
had its real error replaced by advice about a file it does not have. The output has
to name package.json, which both corepack refusals do.

Say what was actually found when a project is set up for more than one manager: a
Pipfile and a [tool.*] table count as commitments too, so naming lockfiles sent the
reader looking for files that are not there.

Note on stderr when no --package-manager was given, since that used to fall through
to npm or pip and now reads the project. Callers parsing output are unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(setup): ask which package manager to use when the project is ambiguous

A project with lockfiles for two managers, or none at all, has no answer we can
read off disk, and picking one is how a yarn project gets installed with npm. The
wizard now asks, and says why it is asking.

Installed managers are listed first and the cursor starts on one, but an
uninstalled manager stays selectable: the choice is the user's and the install
step already warns rather than installing tooling. Projects that state their
manager go straight to the plan.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(setup): keep the package-manager picker inside the terminal

The screen draws a title, the reason for asking and a key hint around the list,
but the list was sized to the whole window, so the hint — including how to go
back — was pushed off the bottom at every terminal size. The list now leaves room
for that chrome, and the list's own help line goes away since the screen prints
its own. Below fourteen rows the reason is dropped: at that size the question and
the choices matter more than the explanation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(setup): show each screen's key hints once, inside the list

The project and environment screens printed a footer of key hints while the list
below already rendered its own help, so every instruction appeared twice. The
wizard's own bindings now go into the list's help line, which is the single place
a screen states them, and the footers are gone.

The package-manager picker follows the same shape, and its height reserve is
retuned for the help line the list now draws, including the extra row that line
takes when the terminal is narrower than it is.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(setup): wrap the plan steps to the terminal width

A step naming an absolute entry-point path alongside the warning that no entry
file was found runs well past a narrow terminal, and overflowing there hides the
warning the step exists to give. Steps now wrap, with what wraps indented under
the number so a step still reads as one item. The screen that names the injected
file wraps for the same reason.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(setup): stop padding pushing the injected file path off screen

Wrapping pads every line to the full width, so the newline left inside the
wrapped lead put a whole row of spaces in front of the file path and carried it
past the edge of the terminal. The newline now sits outside the wrap, and the
closing instruction wraps too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(setup): stop the package-manager list quitting on esc

The list inherits the same quit binding as the others, so esc arriving on its own
ended the session from the picker.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(setup): say the verify step waits for the app as well as the SDK

Verification cannot succeed until the user's application is running, so naming
only the SDK left it unclear whether anything was expected of them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
ffantl-ld added a commit that referenced this pull request Aug 20, 2026
…#782)

* fix(setup): re-derive entry point and package manager on SDK override

Choosing an SDK by hand replaced the detected entry point with that SDK's bare
default in the working directory, so a project whose entry file was src/index.js
got a second index.js created beside it. The package manager was left describing
the language detection guessed first, so a Ruby install never ran bundle add.

Both are now re-derived for the chosen SDK from one shared table of entry-point
candidates, which detection also reads. Snippet-only SDKs have no entry point, so
the final screen no longer names an empty path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(setup): report package-manager confidence and stop guessing silently (#783)

* feat(setup): report package-manager confidence and stop guessing silently

Detection always returned a manager, so a project that never said which one it
used got pip or npm presented as fact. Signals are now split into what the
project declares and what it merely implies, and a verdict is definite only when
the project names exactly one manager.

Recognise the corepack packageManager field, poetry.lock, Pipfile.lock and
[tool.pdm], which were previously read as pip or npm. [tool.hatch] is recorded as
a signal we cannot act on, since hatch has no dependency-add command.

`setup install` now reads the project when --package-manager is omitted, instead
of defaulting to pip or npm whatever the lockfiles say, and fails with the
candidate list when the project is ambiguous rather than picking for the user.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(setup): match nested tool tables and let a committed manager win

Real pyproject files rarely carry a bare [tool.x] header, so matching only that
left the poetry, uv, pdm and hatch signals near-dead: a hatch project configures
[tool.hatch.build], not [tool.hatch]. Nested tables now count, with the trailing
delimiter required so [tool.uv] does not match [tool.uvicorn].

A manager the project committed to now settles the verdict even when an
unactionable tool is also configured. hatchling is a common build backend for uv
and poetry projects, and uv can add the dependency whoever builds the wheel;
previously those projects were marked ambiguous and install refused to run.

Recognise pdm.lock, so a PDM project that commits only its lockfile is still
identified as one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(setup): require a version in the packageManager field

pnpm refuses to run at all when package.json names it without a version — "No
version specified for pnpm in packageManager" — so treating a versionless field
as the project's declared manager routed the user into a command that cannot
work. Such a field is no longer a declaration: the lockfiles decide, or the user
is asked.

When a manager still refuses for that reason, say so. The manifest is malformed
rather than the command wrong, and repairing someone's manifest is not ours to do.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(setup): require one exact version in the packageManager field

Corepack accepts only an exact version, so a range is refused outright —
"Invalid package manager specification in package.json (pnpm@^11.13.0); expected
a semver version" — as is a missing version. Treating either as the project's
declared manager routed the user into a command that cannot run. Only an exact
MAJOR.MINOR.PATCH, optionally with prerelease or build metadata, now counts;
anything else leaves the lockfiles to decide or the user to be asked.

When a manager refuses for that reason, say which part is wrong. The manifest is
malformed rather than the command, and repairing someone's manifest is not ours
to do.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(setup): read pyproject.toml as TOML to find configured tools

Matching [tool.<name>] as text counted comments and strings, so a uv project whose
comment mentioned the tool it migrated away from was marked ambiguous and install
refused to run. Reading the declared tables instead means only a declaration counts,
and nested tables need no special case: TOML creates the parent implicitly, so
[tool.hatch.build] alone still declares hatch.

A file we cannot parse declares nothing, which leaves the project ambiguous and the
user asked — the honest answer when we cannot read what manages it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor(setup): read pyproject.toml once per detection

Detection parsed the file twice for a Python project: once for the detector's own
package manager and again for the confidence verdict. The verdict is now the single
source for the languages it models, and the detector leaves the field to it. A
language it does not model, such as Java's maven versus gradle, keeps the answer its
detector gives.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(setup): stop misreading other ecosystems' install errors

The packageManager guidance matched a missing or non-semver version anywhere in an
install failure, so a gem, a Python package or a Go module reporting either phrase
had its real error replaced by advice about a file it does not have. The output has
to name package.json, which both corepack refusals do.

Say what was actually found when a project is set up for more than one manager: a
Pipfile and a [tool.*] table count as commitments too, so naming lockfiles sent the
reader looking for files that are not there.

Note on stderr when no --package-manager was given, since that used to fall through
to npm or pip and now reads the project. Callers parsing output are unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(setup): ask which package manager to use when the project is ambiguous (#784)

* feat(setup): report package-manager confidence and stop guessing silently

Detection always returned a manager, so a project that never said which one it
used got pip or npm presented as fact. Signals are now split into what the
project declares and what it merely implies, and a verdict is definite only when
the project names exactly one manager.

Recognise the corepack packageManager field, poetry.lock, Pipfile.lock and
[tool.pdm], which were previously read as pip or npm. [tool.hatch] is recorded as
a signal we cannot act on, since hatch has no dependency-add command.

`setup install` now reads the project when --package-manager is omitted, instead
of defaulting to pip or npm whatever the lockfiles say, and fails with the
candidate list when the project is ambiguous rather than picking for the user.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(setup): match nested tool tables and let a committed manager win

Real pyproject files rarely carry a bare [tool.x] header, so matching only that
left the poetry, uv, pdm and hatch signals near-dead: a hatch project configures
[tool.hatch.build], not [tool.hatch]. Nested tables now count, with the trailing
delimiter required so [tool.uv] does not match [tool.uvicorn].

A manager the project committed to now settles the verdict even when an
unactionable tool is also configured. hatchling is a common build backend for uv
and poetry projects, and uv can add the dependency whoever builds the wheel;
previously those projects were marked ambiguous and install refused to run.

Recognise pdm.lock, so a PDM project that commits only its lockfile is still
identified as one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(setup): require a version in the packageManager field

pnpm refuses to run at all when package.json names it without a version — "No
version specified for pnpm in packageManager" — so treating a versionless field
as the project's declared manager routed the user into a command that cannot
work. Such a field is no longer a declaration: the lockfiles decide, or the user
is asked.

When a manager still refuses for that reason, say so. The manifest is malformed
rather than the command wrong, and repairing someone's manifest is not ours to do.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(setup): require one exact version in the packageManager field

Corepack accepts only an exact version, so a range is refused outright —
"Invalid package manager specification in package.json (pnpm@^11.13.0); expected
a semver version" — as is a missing version. Treating either as the project's
declared manager routed the user into a command that cannot run. Only an exact
MAJOR.MINOR.PATCH, optionally with prerelease or build metadata, now counts;
anything else leaves the lockfiles to decide or the user to be asked.

When a manager refuses for that reason, say which part is wrong. The manifest is
malformed rather than the command, and repairing someone's manifest is not ours
to do.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(setup): read pyproject.toml as TOML to find configured tools

Matching [tool.<name>] as text counted comments and strings, so a uv project whose
comment mentioned the tool it migrated away from was marked ambiguous and install
refused to run. Reading the declared tables instead means only a declaration counts,
and nested tables need no special case: TOML creates the parent implicitly, so
[tool.hatch.build] alone still declares hatch.

A file we cannot parse declares nothing, which leaves the project ambiguous and the
user asked — the honest answer when we cannot read what manages it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor(setup): read pyproject.toml once per detection

Detection parsed the file twice for a Python project: once for the detector's own
package manager and again for the confidence verdict. The verdict is now the single
source for the languages it models, and the detector leaves the field to it. A
language it does not model, such as Java's maven versus gradle, keeps the answer its
detector gives.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(setup): stop misreading other ecosystems' install errors

The packageManager guidance matched a missing or non-semver version anywhere in an
install failure, so a gem, a Python package or a Go module reporting either phrase
had its real error replaced by advice about a file it does not have. The output has
to name package.json, which both corepack refusals do.

Say what was actually found when a project is set up for more than one manager: a
Pipfile and a [tool.*] table count as commitments too, so naming lockfiles sent the
reader looking for files that are not there.

Note on stderr when no --package-manager was given, since that used to fall through
to npm or pip and now reads the project. Callers parsing output are unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(setup): ask which package manager to use when the project is ambiguous

A project with lockfiles for two managers, or none at all, has no answer we can
read off disk, and picking one is how a yarn project gets installed with npm. The
wizard now asks, and says why it is asking.

Installed managers are listed first and the cursor starts on one, but an
uninstalled manager stays selectable: the choice is the user's and the install
step already warns rather than installing tooling. Projects that state their
manager go straight to the plan.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(setup): keep the package-manager picker inside the terminal

The screen draws a title, the reason for asking and a key hint around the list,
but the list was sized to the whole window, so the hint — including how to go
back — was pushed off the bottom at every terminal size. The list now leaves room
for that chrome, and the list's own help line goes away since the screen prints
its own. Below fourteen rows the reason is dropped: at that size the question and
the choices matter more than the explanation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(setup): show each screen's key hints once, inside the list

The project and environment screens printed a footer of key hints while the list
below already rendered its own help, so every instruction appeared twice. The
wizard's own bindings now go into the list's help line, which is the single place
a screen states them, and the footers are gone.

The package-manager picker follows the same shape, and its height reserve is
retuned for the help line the list now draws, including the extra row that line
takes when the terminal is narrower than it is.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(setup): wrap the plan steps to the terminal width

A step naming an absolute entry-point path alongside the warning that no entry
file was found runs well past a narrow terminal, and overflowing there hides the
warning the step exists to give. Steps now wrap, with what wraps indented under
the number so a step still reads as one item. The screen that names the injected
file wraps for the same reason.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(setup): stop padding pushing the injected file path off screen

Wrapping pads every line to the full width, so the newline left inside the
wrapped lead put a whole row of spaces in front of the file path and carried it
past the edge of the terminal. The newline now sits outside the wrap, and the
closing instruction wraps too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(setup): stop the package-manager list quitting on esc

The list inherits the same quit binding as the others, so esc arriving on its own
ended the session from the picker.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(setup): say the verify step waits for the app as well as the SDK

Verification cannot succeed until the user's application is running, so naming
only the SDK left it unclear whether anything was expected of them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to 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