Skip to content

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

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

feat(setup): report package-manager confidence and stop guessing silently#783
ffantl-ld merged 7 commits into
fix/setup-rederive-on-overridefrom
feat/setup-pm-confidence

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

Steps 1–3 of the package-manager selection design. Stacked on #782, which owns PackageManagerFor. The wizard picker (step 4) is a follow-up.

Describe the solution you've provided

  • Split package-manager signals into what a project declares and what it merely implies, and report a definite/ambiguous verdict. Detection previously always returned a manager, so a project that never said which one it used got pip or npm presented as fact.
  • Recognise signals that were previously read as pip/npm: the corepack packageManager field (which outranks lockfiles, being the most explicit statement a project can make), poetry.lock, Pipfile.lock, package-lock.json, npm-shrinkwrap.json, and [tool.pdm].
  • Record [tool.hatch] as a signal we cannot act on — hatch has no dependency-add command — so a hatch project stays ambiguous instead of silently falling through to pip.
  • setup install reads the project when --package-manager is omitted, rather than defaulting to pip/npm whatever the lockfiles say. On an ambiguous project it fails with the candidate list instead of choosing for the user.
  • setup detect reports the verdict, and its JSON adds package_manager_candidates with the command each would run and whether it is installed.

Describe alternatives you've considered

  • Improving detection signals only, without a confidence verdict. Rejected: a repo with both yarn.lock and package-lock.json has no correct guess, and today detectNodePM returns yarn purely because it checks first. No amount of signal tuning fixes a project that contradicts itself.
  • Letting installed tooling decide — auto-selecting when exactly one candidate is on PATH. Rejected: having pnpm installed says nothing about whether this repo uses it, and it would give two developers different answers for the same project. Installed state orders the list and nothing more.
  • Guessing with a low-confidence marker in setup install rather than failing. Rejected: these subcommands are the documented engine for external agents, and a silent wrong manager is worse than an error.

Additional context

PackageManagerCandidates is deliberately not on DetectResult. installed describes the machine rather than the project, so keeping it out leaves detection deterministic and its tests machine-independent; the detect command adds candidates to its own JSON payload for callers that need them.

Installed tracks the executable the resolved command actually runs, not the candidate label — the pip candidate reports installed when only pip3 exists, because its command is pip3 install.

DetectResult.PackageManager is still whatever the language detector produced. Those detectors know about managers this model does not, such as maven versus gradle.

Testing approaches

  • go test ./... passes.
  • Confidence tests over definite shapes (corepack with and without a version, corepack beating a conflicting lockfile, single lockfiles, [tool.uv], [tool.pdm], Gemfile) and ambiguous ones (conflicting lockfiles, bare package.json, PEP 621 pyproject.toml only, requirements.txt only, hatch, gemspec without a Gemfile).
  • A test pinning that installed state cannot change the verdict: with only pnpm on a stubbed PATH, a bare package.json stays ambiguous and the name stays npm.
  • TestFileDetector_ProjectShapes clears the confidence fields before comparing, since those shapes assert language/SDK/entry point and confidence has its own tests.
  • Verified with a built binary on the four cases that were previously silently wrong: corepack pnpm@9.1.0pnpm (was npm); yarn.lock + package-lock.json → flagged uncertain naming both (was silently yarn); [tool.pdm]pdm (was pip); [tool.hatch] → uncertain (was pip). setup install fails on the conflicting project and resolves uv add in a uv.lock project where it previously ran pip3 install.

Note

Overview
Stops treating a conventional default (npm/pip) as fact. Detection now returns a definite or ambiguous package-manager verdict, and setup install uses that instead of guessing.

Detection reads declared vs implied signals: corepack packageManager (exact semver only, outranks lockfiles), extra lockfiles (package-lock.json, poetry.lock, pdm.lock, etc.), and parsed [tool.*] tables in pyproject.toml. Conflicting or missing signals, hatch-only projects, and invalid corepack specs stay ambiguous. Installed tools never decide the verdict.

setup detect surfaces uncertainty in plaintext and, for JSON, adds package_manager_candidates (command + installed) only when ambiguous. setup install without --package-manager auto-selects a definite manager (stderr note about the old npm/pip default) and errors on ambiguous projects with a candidate list.

Also adds pdm add install support and maps corepack “bad packageManager spec” failures to a recoverable reason instead of a dead-end error.

Reviewed by Cursor Bugbot for commit 04662ac. 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 18:43
Comment thread internal/setup/detector.go
Comment thread internal/setup/detector.go
Comment thread internal/setup/detector.go Outdated
@ffantl-ld
ffantl-ld force-pushed the fix/setup-rederive-on-override branch from 47efc96 to 4129348 Compare August 18, 2026 15:29
@ffantl-ld
ffantl-ld force-pushed the feat/setup-pm-confidence branch from 6df6b57 to 3cfd103 Compare August 18, 2026 15:29
Comment thread internal/setup/installer.go
@ffantl-ld
ffantl-ld force-pushed the fix/setup-rederive-on-override branch from 4129348 to b94d3d1 Compare August 19, 2026 14:12
@ffantl-ld
ffantl-ld force-pushed the feat/setup-pm-confidence branch from 055976e to 7fef87c Compare August 19, 2026 14:12
Comment thread cmd/setup/install.go
@ffantl-ld
ffantl-ld force-pushed the fix/setup-rederive-on-override branch from b94d3d1 to 88af10e Compare August 19, 2026 15:51
@ffantl-ld
ffantl-ld force-pushed the feat/setup-pm-confidence branch from 7fef87c to 87e9ed0 Compare August 19, 2026 15:51
Comment thread internal/setup/detector.go
@ffantl-ld
ffantl-ld force-pushed the fix/setup-rederive-on-override branch from 88af10e to 9c8a11b Compare August 19, 2026 16:09
@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 fix/setup-rederive-on-override branch from 9c8a11b to 1777210 Compare August 19, 2026 16:52
@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 fix/setup-rederive-on-override branch from 1777210 to d8a6410 Compare August 19, 2026 17:39
@ffantl-ld
ffantl-ld force-pushed the feat/setup-pm-confidence branch from b4e5252 to f0a249f Compare August 19, 2026 17:39
Comment thread internal/setup/detector.go Outdated
@ffantl-ld
ffantl-ld force-pushed the fix/setup-rederive-on-override branch from d8a6410 to 616844c Compare August 19, 2026 18:18
@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-confidence branch from acfb88b to 6446556 Compare August 19, 2026 21:14
@ffantl-ld
ffantl-ld force-pushed the fix/setup-rederive-on-override branch 2 times, most recently from b3c3950 to 9138af9 Compare August 20, 2026 15:13
@ffantl-ld
ffantl-ld force-pushed the feat/setup-pm-confidence branch from 6446556 to d56b2d7 Compare August 20, 2026 15:13

@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 d56b2d7. Configure here.

Comment thread cmd/setup/install.go
ffantl-ld and others added 7 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>
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>
@ffantl-ld
ffantl-ld force-pushed the fix/setup-rederive-on-override branch from 9138af9 to e76ec60 Compare August 20, 2026 17:18
@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 merged commit f4e67c1 into fix/setup-rederive-on-override Aug 20, 2026
9 checks passed
@ffantl-ld
ffantl-ld deleted the feat/setup-pm-confidence branch August 20, 2026 17:31
ffantl-ld added a commit that referenced this pull request Aug 20, 2026
…ntly (#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>
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