Skip to content

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

Open
ffantl-ld wants to merge 7 commits into
fix/setup-rederive-on-overridefrom
feat/setup-pm-confidence
Open

feat(setup): report package-manager confidence and stop guessing silently#783
ffantl-ld wants to merge 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
Setup no longer treats npm/pip as facts when the repo never declared a manager. Detection now separates declared signals (corepack packageManager, lockfiles, [tool.*] in pyproject.toml) from guesses and adds package_manager_confidence (definite vs ambiguous) plus a user-facing reason on DetectResult.

setup detect marks uncertain managers in plaintext and, in JSON, adds package_manager_candidates (command + whether the tool is on PATH) when ambiguous—without putting machine-specific installed on the core detect struct.

setup install without --package-manager reads the project via PackageManagerChoiceFor; ambiguous projects error with candidate names (not installed tools flagged). When auto-selection succeeds, a one-time stderr note explains the behavior change from blind npm/pip defaults. Install plaintext/JSON can surface Warning from pip installs that don’t update requirements.txt, and the wizard success screen shows that note.

Wizard SDK override re-derives entry point and package manager for the chosen SDK (e.g. Ruby gets gem/bundle, not Node’s pnpm) using shared EntryPointFor / PackageManagerFor tables.

Installer adds PDM support, parses pyproject.toml with TOML (not substring matching), handles invalid corepack specs in failure text, and improves venv pip path resolution on Windows.

Tests add CallCmdCapturingStderr and broad confidence/signal coverage.

Reviewed by Cursor Bugbot for commit 6446556. 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 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 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 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 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-confidence branch from 9246e77 to b4e5252 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

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

Comment thread internal/setup/detector.go Outdated
@ffantl-ld
ffantl-ld force-pushed the feat/setup-pm-confidence branch from f0a249f to c10867f Compare August 19, 2026 18:18
ffantl-ld and others added 5 commits August 19, 2026 17:09
…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>
ffantl-ld and others added 2 commits August 19, 2026 17:10
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 feat/setup-pm-confidence branch from acfb88b to 6446556 Compare August 19, 2026 21:14
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