Skip to content

fix(resolve): forced PM biases task selection#71

Merged
kjanat merged 2 commits into
masterfrom
fix/pm-override-task-source-bias
Jun 28, 2026
Merged

fix(resolve): forced PM biases task selection#71
kjanat merged 2 commits into
masterfrom
fix/pm-override-task-source-bias

Conversation

@kjanat

@kjanat kjanat commented Jun 27, 2026

Copy link
Copy Markdown
Owner

Problem

When a task name exists in multiple sources (e.g. package.json:check and deno:check) and the user forces a package manager that is also a task source via --pm / RUNNER_PM, source selection still resolved the conflict by the default tier (package.json wins over deno) and then ran that package.json script through the forced PM (RUNNER_PM=deno run checkdeno task check). That is a broken dispatch even though a native deno:check exists, because deno cannot honor the npm lifecycle/build artifacts the package.json script depends on (e.g. @biomejs/cli-* platform binary never built → "Cannot find module" crash).

Forcing a PM should bias task selection toward that PM's own source.
See #70.

Change

Generalize the forced-PM source bias to every package manager — deno is one member of the rule, not a special case.

  • New PackageManager::owned_task_sources() (src/types.rs): a total PM → task-source(s) map, most-native first. npm/yarn/pnpm/bun → [package.json]; deno → [deno.json, package.json] (native first, also-runs second); cargo → [CargoAliases]; go → [GoPackage]; uv/poetry/pipenv → [pyproject scripts]; bundler/composer → [] (no task source modeled, so they bias nothing). Replaces the deno-only distinct_task_source().
  • The bias is folded into source_priority (src/cmd/run/select.rs), not added as a new tuple component. A forced PM's owned sources win same-name conflicts ranked by position (native = 0, next = 1); every other source drops below them (saturating_add(owned.len())). Because cmd::why and doctor already render source_priority, their explanations stay truthful with zero JSON/schema churn.
  • Surgical scope: the unforced path (base_source_priority) is byte-for-byte identical to prior behavior; single-candidate lookups are unaffected (only one task to pick); only --pm/RUNNER_PM (the cross-ecosystem overrides.pm) triggers it. runner.toml PM overrides (pm_by_ecosystem) and prefer_runners are untouched.

Verification

Gates (all green):

  • cargo build
  • cargo test --lib — 709 pass; src/cmd/run/select.rs carries 10 tests (5 new for the generalization: bun-biases-package.json, bun-beats-turbo, deno-falls-back-to-package.json, forced-PM-without-owned-source, bun-priority-zero).
  • cargo clippy --all-targets --all-features (exit 0)
  • dprint check (exit 0 on tracked files)

End-to-end with the built binary against a fixture carrying both package.json:check and deno.json:check (runner why check):

Out of scope (follow-ups): a dedicated runner axis that accepts the PMs as runners, and lifecycle-script detection.

Closes #70

@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 28158983-36af-438b-bc18-52510fa9b68a

📥 Commits

Reviewing files that changed from the base of the PR and between b763a72 and 5dd8229.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • src/cmd/run/select.rs
  • src/types.rs
📜 Recent review details
⚠️ CI failures not shown inline (5)

GitHub Actions: verify PR #71 / verify: verify PR #71

Conclusion: failure

View job details

##[group]Run bail() {
 �[36;1mbail() {�[0m
 �[36;1m  printf '::error::install-action: %s\n' "$*"�[0m

GitHub Actions: verify PR #71 / verify: verify PR #71

Conclusion: failure

View job details

test cmd::tests::group_emission_gates_on_nesting ... ok
 test cmd::tests::prepended_path_handles_missing_parent ... ok
 test cmd::tests::prepended_path_orders_bins_before_parent ... ok
 test cmd::tests::should_group_requires_both_opt_in_and_github_actions ... ok
 test cmd::tests::node_bin_dirs_walks_ancestors_nearest_first ... ok
 test cmd::tests::node_bin_dirs_requires_bin_subdir ... ok
 test cmd::tests::task_timing_summary_includes_duration_and_exit_code ... ok
 test cmd::tests::timing_enabled_respects_quiet_and_no_warnings ... ok
 test cmd::why::tests::v3_report_collects_sibling_aliases ... ok
 test cmd::why::tests::v3_report_ranks_multiple_candidates ... ok
 test cmd::why::tests::v3_report_describes_cargo_alias_like_the_committed_example ... ok
 test cmd::why::tests::v3_report_resolves_selected_pyproject_script_through_python_pm ... ok
 test cmd::why::tests::v3_report_uses_exec_fallback_decision_when_nothing_matches ... ok
 test cmd::tests::spawn_resolves_dev_dependency_binary_via_child_path ... ok
 test cmd::why::tests::why_handles_missing_task ... ok
 test cmd::why::tests::why_pyproject_script_reports_detected_python_pm ... ok
 test cmd::why::tests::why_pyproject_script_reports_python_pm_override ... ok
 test cmd::why::tests::why_rejects_runner_constraint_mismatch ... ok
 test complete::tests::detect_path_files_handles_short_value_flag_attached ... ok
 test complete::tests::detect_path_files_handles_short_value_flag_separated ... ok
 test complete::tests::detect_path_files_honours_boolean_shadow_on_subcommand ... ok
 test complete::tests::detect_path_files_ignores_boolean_short_flag ... ok
 test cmd::why::tests::why_with_multiple_candidates_renders_both_formats ... ok
 test complete::tests::detect_path_files_ignores_non_path_flags ... ok
 test complete::tests::detect_path_files_recognises_separated_dir_flag ... ok
 test complete::tests::detect_path_files_recognises_root_flag_after_subcommand ... ok
 test complete::tests::detect_path_files_respects_active_su...

GitHub Actions: verify PR #71 / 9_verify.txt: verify PR #71

Conclusion: failure

View job details

##[group]Run bail() {
 �[36;1mbail() {�[0m
 �[36;1m  printf '::error::install-action: %s\n' "$*"�[0m

GitHub Actions: PR #71 / 2_Analyze (rust).txt: PR #71

Conclusion: failure

View job details

for 'assert'
 [] [build-stdout] [] [build-stdout] �[33m WARN�[0m /home/runner/work/runner/runner/src/types.rs:979:9: macro expansion failed for 'assert'
 [] [build-stdout] [] [build-stdout] �[33m WARN�[0m /home/runner/work/runner/runner/src/types.rs:980:9: macro expansion failed for 'assert'
 [] [build-stdout] [] [build-stdout] �[33m WARN�[0m /home/runner/work/runner/runner/src/types.rs:981:9: macro expansion failed for 'assert'
 [] [build-stdout] [] [build-stdout] �[33m WARN�[0m /home/runner/work/runner/runner/src/types.rs:988:9: macro expansion failed for 'assert'
 [] [build-stdout] [] [build-stdout] �[33m WARN�[0m /home/runner/work/runner/runner/src/types.rs:989:9: macro expansion failed for 'assert'
 [] [build-stdout] [] [build-stdout] �[33m WARN�[0m /home/runner/work/runner/runner/src/types.rs:990:9: macro expansion failed for 'assert'
 [] [build-stdout] [] [build-stdout] �[33m WARN�[0m /home/runner/work/runner/runner/src/types.rs:991:9: macro expansion failed for 'assert'
 [] [build-stdout] [] [build-stdout] �[33m WARN�[0m /home/runner/work/runner/runner/src/types.rs:992:9: macro expansion failed for 'assert'
 [] [build-stdout] [] [build-stdout] �[33m WARN�[0m /home/runner/work/runner/runner/src/types.rs:997:9: macro expansion failed for 'assert'
 [] [build-stdout] [] [build-stdout] �[33m WARN�[0m /home/runner/work/runner/runner/src/types.rs:998:9: macro expansion failed for 'assert'
 [] [build-stdout] [] [build-stdout] �[33m WARN�[0m /home/runner/work/runner/runner/src/types.rs:1003:9: macro expansion failed for 'assert'
 [] [build-stdout] [] [build-stdout] �[33m WARN�[0m /home/runner/work/runner/runner/src/types.rs:1008:9: macro expansion failed for 'assert'
 [] [build-stdout] [] [build-stdout] �[33m WARN�[0m /home/runner/work/runner/runner/src/types.rs:1009:9: macro expansion failed for 'assert'
 [] [build-stdout] [] [build-stdout] �[33m WARN�[0m /home/runner/work/runner/runner/src/types.rs:1010:9: macro expansion failed for 'assert'
 [] [build-stdout] [...

GitHub Actions: PR #71 / Analyze (rust): PR #71

Conclusion: failure

View job details

n failed for 'assert'
 [] [build-stdout] [] [build-stdout] �[33m WARN�[0m /home/runner/work/runner/runner/src/types.rs:929:9: macro expansion failed for 'assert'
 [] [build-stdout] [] [build-stdout] �[33m WARN�[0m /home/runner/work/runner/runner/src/types.rs:930:9: macro expansion failed for 'assert'
 [] [build-stdout] [] [build-stdout] �[33m WARN�[0m /home/runner/work/runner/runner/src/types.rs:931:9: macro expansion failed for 'assert'
 [] [build-stdout] [] [build-stdout] �[33m WARN�[0m /home/runner/work/runner/runner/src/types.rs:937:9: macro expansion failed for 'assert'
 [] [build-stdout] [] [build-stdout] �[33m WARN�[0m /home/runner/work/runner/runner/src/types.rs:938:9: macro expansion failed for 'assert'
 [] [build-stdout] [] [build-stdout] �[33m WARN�[0m /home/runner/work/runner/runner/src/types.rs:939:9: macro expansion failed for 'assert'
 [] [build-stdout] [] [build-stdout] �[33m WARN�[0m /home/runner/work/runner/runner/src/types.rs:940:9: macro expansion failed for 'assert'
 [] [build-stdout] [] [build-stdout] �[33m WARN�[0m /home/runner/work/runner/runner/src/types.rs:941:9: macro expansion failed for 'assert'
 [] [build-stdout] [] [build-stdout] �[33m WARN�[0m /home/runner/work/runner/runner/src/types.rs:946:9: macro expansion failed for 'assert'
 [] [build-stdout] [] [build-stdout] �[33m WARN�[0m /home/runner/work/runner/runner/src/types.rs:947:9: macro expansion failed for 'assert'
 [] [build-stdout] [] [build-stdout] �[33m WARN�[0m /home/runner/work/runner/runner/src/types.rs:948:9: macro expansion failed for 'assert'
 [] [build-stdout] [] [build-stdout] �[33m WARN�[0m /home/runner/work/runner/runner/src/types.rs:953:9: macro expansion failed for 'assert'
 [] [build-stdout] [] [build-stdout] �[33m WARN�[0m /home/runner/work/runner/runner/src/types.rs:954:9: macro expansion failed for 'assert'
 [] [build-stdout] [] [build-stdout] �[33m WARN�[0m /home/runner/work/runner/runner/src/types.rs:955:9: macro expansion failed for 'assert'
 [] [build-stdo...
🧰 Additional context used
📓 Path-based instructions (1)
**/CHANGELOG.md

📄 CodeRabbit inference engine (Custom checks)

**/CHANGELOG.md: If any source code files (excluding tests, docs, CI, markdown, or comments-only changes) are modified, CHANGELOG.md MUST also be modified in the same PR.
If a version bump is detected, CHANGELOG.md MUST contain a new section header matching the exact new version number in the format: '## [X.Y.Z] - YYYY-MM-DD'.
If NO version bump is detected, the changes in the PR MUST be added under the existing '## [Unreleased]' section in CHANGELOG.md. The entry MUST describe the changes (e.g., Added, Changed, Fixed, Removed).

Files:

  • CHANGELOG.md
🧠 Learnings (7)
📚 Learning: 2026-03-26T20:05:44.851Z
Learnt from: kjanat
Repo: kjanat/runner PR: 1
File: src/cmd/mod.rs:67-75
Timestamp: 2026-03-26T20:05:44.851Z
Learning: In Rust, `std::process::Command` does not provide getters for stdio configuration, so whether `Stdio::inherit()` (or other `Stdio::*` settings) was applied cannot be asserted via pure unit tests without spawning a process and inspecting OS-level fds. When reviewing Rust code, do not flag “missing unit test coverage” for stdio configuration on `std::process::Command` as a code issue—treat the explicit `Command::stdin/stdout/stderr` setter calls in the source as the meaningful guarantee.

Applied to files:

  • src/types.rs
  • src/cmd/run/select.rs
📚 Learning: 2026-04-21T15:16:40.277Z
Learnt from: kjanat
Repo: kjanat/runner PR: 4
File: src/tool/just.rs:132-133
Timestamp: 2026-04-21T15:16:40.277Z
Learning: In Rust, if a method like `ExtractedTask::name() -> &str` returns a borrowed `&str` tied to `&self`, then using `sort_unstable_by_key(|t| t.name())` should be avoided because `by_key` requires the key to not borrow from the element being sorted (it will fail to compile due to the key’s lifetime). Do not recommend `sort_unstable_by_key` as a simplification in this situation. If you need an allocation-free and idiomatic comparison, use `sort_unstable_by(|a, b| a.name().cmp(b.name()))` instead. Using `.to_owned()` inside `by_key` is an alternative but allocates a `String` per element.

Applied to files:

  • src/types.rs
  • src/cmd/run/select.rs
📚 Learning: 2026-05-04T23:28:17.947Z
Learnt from: kjanat
Repo: kjanat/runner PR: 5
File: src/lib.rs:222-223
Timestamp: 2026-05-04T23:28:17.947Z
Learning: When reviewing Rust `rustdoc` comments (`/// ...`), treat a trailing backslash (`\`) at the end of a comment line as valid CommonMark syntax for a hard line break (rendered as `<br>`). Do not flag such trailing backslashes as papercuts or recommend removing them unless there is clear evidence they are unintended (e.g., they are not in the `rustdoc` comment context or the surrounding formatting contradicts the intended CommonMark hard-break usage).

Applied to files:

  • src/types.rs
  • src/cmd/run/select.rs
📚 Learning: 2026-06-01T17:42:48.461Z
Learnt from: kjanat
Repo: kjanat/runner PR: 34
File: Cargo.toml:61-61
Timestamp: 2026-06-01T17:42:48.461Z
Learning: In this repo, do not flag `actions_rs::log::GroupGuard` or `actions_rs::log::group_guard` as missing/non-existent when they are referenced in Rust code, because the `actions-rs` crate (dependency `actions-rs = "0.1"` / published v0.1.x) exports `pub struct GroupGuard` and `pub fn group_guard` from `actions_rs::log` (and also exports `actions_rs::env::is_github_actions`). This avoids false positives from outdated/incorrect web search results. If the repo does not depend on `actions-rs = "0.1"` in `Cargo.toml`, then normal missing-import/item checks can apply.

Applied to files:

  • src/types.rs
  • src/cmd/run/select.rs
📚 Learning: 2026-06-11T18:52:28.233Z
Learnt from: kjanat
Repo: kjanat/runner PR: 45
File: src/lib.rs:659-661
Timestamp: 2026-06-11T18:52:28.233Z
Learning: In Rust, it’s acceptable to use `matches!(cli.command, Some(cli::Command::Doctor { .. }))` (or similar) when the scrutinee comes from a field accessed through a shared reference (e.g., `cli: &Cli`). If the match pattern uses `..` and binds zero fields (so only the enum discriminant is matched), Rust match ergonomics will avoid moving out of the referenced value. Reviewers should not flag such code as a compile-blocking move; confirm with `cargo check` rather than forcing changes like `.as_ref()` when compilation succeeds.

Applied to files:

  • src/types.rs
  • src/cmd/run/select.rs
📚 Learning: 2026-05-15T01:31:48.037Z
Learnt from: kjanat
Repo: kjanat/runner PR: 27
File: src/types.rs:584-585
Timestamp: 2026-05-15T01:31:48.037Z
Learning: In the `kjanat/runner` Rust codebase, remember that `just` treats its `justfile` name as officially case-insensitive and allows the hidden variant: `justfile`, `Justfile`, `JUSTFILE`, etc., and `.justfile`. `TaskSource::Justfile` detection should recognize these filename variants. Also note that `from_label` in `src/types.rs` is for parsing user-supplied qualifier prefixes (e.g. `justfile:build`), not for detecting the on-disk justfile. If you add/adjust qualifier label emission for `TaskSource::Justfile`, it’s not strictly required for backward compatibility because `TaskSource::label()` previously only emitted lowercase `"justfile"`, but adding additional casing/hidden variants as a defensive UX improvement is consistent with `just`’s official support.

Applied to files:

  • src/types.rs
  • src/cmd/run/select.rs
📚 Learning: 2026-05-14T15:35:39.922Z
Learnt from: kjanat
Repo: kjanat/runner PR: 27
File: src/cmd/run.rs:206-212
Timestamp: 2026-05-14T15:35:39.922Z
Learning: For Go projects using the canonical `cmd/` layout, when `runner` dispatches a local Go tool by its bare name (e.g., `some-cli`) under `PackageManager::Go`, resolve it relative to `ctx.root` by probing `ctx.root/cmd/<task_name>/`. If it exists, run the tool with the filesystem-path form: `go run ./cmd/<task_name>` (note the leading `./`). Do NOT generate `go run <task_name>` because bare names are treated as Go import paths, not local directories. While `go run <module>/cmd/<task_name>` can work, it’s unnecessary for local development—prefer `./cmd/<task_name>`.

Applied to files:

  • src/cmd/run/select.rs
🪛 GitHub Actions: PR `#71` / 2_Analyze (rust).txt
src/types.rs

[warning] 979-979: Rust extraction warning: macro expansion failed for 'assert' (warning).


[warning] 980-980: Rust extraction warning: macro expansion failed for 'assert' (warning).


[warning] 981-981: Rust extraction warning: macro expansion failed for 'assert' (warning).


[warning] 1052-1052: Rust extraction warning: macro expansion failed for 'assert_eq' (warning).

🪛 GitHub Actions: PR `#71` / Analyze (rust)
src/types.rs

[warning] 929-1052: Rust compiler/CodeQL extraction warning: macro expansion failed for 'assert' (also mentions assert_eq near line 1052).

🔍 Remote MCP GitHub Grep

Relevant review context

  • I did not find a public GitHub hit for the new owned_task_sources( symbol, which suggests it is likely repo-specific/new.
  • Public Rust code commonly uses a source_priority(...) helper as a deterministic ranking function that is then applied during sort_by/then_with ordering. Examples found in unrelated repos include openobserve/openobserve and microsoft/windows-rs-adjacent code patterns, where lower numeric priority wins.
  • I also found unrelated examples where source priority is used as a stable tie-breaker in merge/select flows, which is consistent with the PR’s approach of folding the forced-PM bias into ranking rather than adding a separate ad hoc branch.
🔇 Additional comments (3)
src/types.rs (1)

540-561: LGTM!

src/cmd/run/select.rs (1)

11-16: LGTM!

Also applies to: 48-56, 66-81, 111-117, 272-335, 350-360

CHANGELOG.md (1)

28-39: LGTM!


📝 Walkthrough

Bias forced package-manager conflict resolution toward that manager’s own task source.

When --pm/RUNNER_PM is set and the same task name exists in multiple sources, the resolver now prefers the forced package manager’s native task source first instead of always following the default source order. This is generalised across supported package managers via PackageManager::owned_task_sources(), with source_priority using that mapping for both selection and diagnostics.

Unforced resolution remains unchanged, single-candidate lookups are unaffected, and runner.toml PM overrides do not participate in this bias.

Walkthrough

A new owned_task_sources method is added to PackageManager, returning the task sources a given PM natively owns in priority order. The source_priority function in the task selector is updated to extract an optional forced PM from CLI/env overrides and, when present, re-tier same-name conflicting tasks so the forced PM's owned sources win (lowest index = highest priority), while all other sources are shifted downward using saturating arithmetic. A new private forced_pm helper isolates this bias to cross-ecosystem CLI/env overrides only, excluding runner.toml PM overrides. Six unit tests are added, and the fix is recorded in CHANGELOG.md.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

  • #70 — This PR directly implements the proposed direction 1 from that issue: --pm/RUNNER_PM now biases source selection toward the forced PM's own task sources for same-name conflicts, fixing the broken RUNNER_PM=deno run checkpackage.json:check dispatch.

Suggested labels

bug, area: cli

Poem

Arrr, the cursèd conflict be fixed at last, ye scallywags! 🏴‍☠️
When RUNNER_PM=deno rang across the sea,
The wrong task source'd steal the victory~! 💢
But now owned_task_sources charts the true course,
DenoJson claims priority zero, of course!
No more package.json runnin' through deno's hull,
The right task wins — and the seas are beautiful~ 🌊✨

🚥 Pre-merge checks | ✅ 7 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Semver Version Bump Validation ⚠️ Warning Source files changed, but Cargo.toml stays at 0.14.3 on both base and HEAD, so there’s no SemVer bump for this bug fix. Bump the crate version in Cargo.toml from 0.14.3 to the next PATCH release (for example 0.14.4) and include that in the PR.
✅ Passed checks (7 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise, uses fix:, and matches the forced-PM task-selection bug.
Description check ✅ Passed The description clearly explains the same conflict-resolution bug and its fix.
Linked Issues check ✅ Passed The implementation now prefers the forced PM's own task source on conflicts, satisfying #70.
Out of Scope Changes check ✅ Passed No unrelated changes stand out; the edits all support the forced-PM source-bias fix.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 30.00%.
Changelog Update ✅ Passed Source files changed in src/, and CHANGELOG.md was updated under ## [Unreleased] with a ### Fixed entry covering the PM-bias change.
Agents.Md Documentation Updated ✅ Passed No AGENTS.md files exist in the repo, so these CLI/behaviour changes needed no AGENTS update.

Comment @coderabbitai help to get the list of available commands.

When a task name exists in multiple sources (e.g. package.json:check and
deno:check) and the user forces a package manager that is also a distinct
task source via --pm deno / RUNNER_PM=deno, source selection kept picking
package.json per the default tier and then ran that script through deno
(deno task check), which breaks when the script relies on npm lifecycle
build artifacts deno cannot honor.

Fold a forced-PM source bias into source_priority: a --pm / RUNNER_PM
override naming a package manager that owns a distinct task source (today
only deno, via PackageManager::distinct_task_source -> DenoJson) wins its
own same-name conflicts outright (priority 0); every other source drops
one tier. The bias lives inside source_priority so cmd::why and doctor,
which render that number, stay truthful with no schema churn. The unforced
path is byte-for-byte identical to before, and single-candidate lookups
are unaffected, so only genuine conflicts re-order.

Verification: cargo build, cargo test --lib (705 pass, 6 new), cargo
clippy --all-targets --all-features (exit 0), dprint check (exit 0).
End-to-end with the built binary: RUNNER_PM=deno run check dispatches
`deno task check`; run check (no override) still selects package.json;
run deno:check FQN still works.

Closes #70
@kjanat kjanat force-pushed the fix/pm-override-task-source-bias branch from c88de8a to b763a72 Compare June 27, 2026 18:11
@kjanat kjanat self-assigned this Jun 27, 2026
@coderabbitai coderabbitai Bot added area: cli Argument parsing, commands, and CLI UX bug Something isn't working labels Jun 27, 2026
@kjanat kjanat added the tech debt Known compromise or shortcut to revisit later label Jun 27, 2026
@kjanat

kjanat commented Jun 27, 2026

Copy link
Copy Markdown
Owner Author

Closing: this is the wrong fix for a real gap.

What this PR does. distinct_task_source() returns Some(DenoJson) for deno and None for every other PM, and source_priority() uses it so that forcing --pm deno pulls deno.json ahead in a same-name task conflict. Only deno gets this. Two problems with that.

1. It special-cases deno. npm, yarn, pnpm, bun and deno all run scripts, and they do not run them the same way (each has its own shell, lifecycle, and runtime semantics). There is no principled reason deno alone should steer task resolution.

2. It re-ranks task selection with the package-manager flag. --pm selects a package manager (install, exec, lockfiles). Letting it decide which task wins couples two independent capabilities.

The real gap underneath. Choosing which tool runs a script is a runner concern, but there is no runner concept for the script-running PMs. The TaskRunner enum is {Turbo, Nx, Make, Just, GoTask, Mise, Bacon} — npm/yarn/pnpm/bun/deno are not in it. Today the only way to pick among them is --pm (package.json dispatch routes through resolve_node_pm()), so a runner choice has to be smuggled through the package-manager selector. That is the actual conflation, and since the runners are not interchangeable, the inability to pick one cleanly is a genuine gap.

Correct direction. Model npm/yarn/pnpm/bun/deno as runners too and let them be chosen on the runner axis (the same --runner selection that already covers Make/Just/Turbo), decoupled from --pm. Then --pm stays package-only, the runner choice is explicit and covers every script-runner (not deno alone), and task selection is never re-ranked by the package-manager flag.

Closing in favor of that. Tracking the runner-axis work separately.

@kjanat kjanat closed this Jun 27, 2026
@kjanat kjanat added the invalid This doesn't seem right label Jun 27, 2026
Generalize the forced-PM source bias from deno-only to every PM.
`PackageManager::owned_task_sources` maps each PM to the task source(s)
it runs natively, most-native first; `source_priority` ranks a forced
`--pm`/`RUNNER_PM`'s owned sources ahead of all others on a same-name
conflict. Deno is now one member of the rule, not a special case; a PM
owning no modeled source (Bundler, Composer) re-orders nothing.

`RUNNER_PM=deno run check` now selects deno.json's task (`deno task
check`) instead of running a package.json script through deno and
crashing; `--pm bun` likewise pulls package.json to the front.

Replaces deno-only `distinct_task_source` with `owned_task_sources`.
Closes #70.
@kjanat kjanat reopened this Jun 28, 2026
@kjanat

kjanat commented Jun 28, 2026

Copy link
Copy Markdown
Owner Author

Reopened with the fix generalized — it now biases same-name task selection toward any forced PM's own source, not deno alone.

  • distinct_task_source (deno → Some(DenoJson), None for everything else) is replaced by PackageManager::owned_task_sources: a total PM → source(s) map, most-native first (npm/yarn/pnpm/bun → package.json; deno → deno.json, then package.json; cargo/go/Python PMs → their own source; bundler/composer → none).
  • source_priority ranks the forced PM's owned sources native-first; a PM that owns no modeled source re-orders nothing.

RUNNER_PM=deno run check now selects deno.json's task (deno task check) instead of running the package.json script through deno (the #70 crash); --pm bun pulls package.json to the front the same way. Deno is one member of the rule now, not a special case.

709 lib tests pass (5 new generalization tests), clippy + dprint clean. Closes #70.

@kjanat kjanat changed the title fix(resolve): --pm deno/RUNNER_PM=deno biases same-name conflicts to deno's task source fix(resolve): bias same-name selection to any forced PM's own source Jun 28, 2026
@kjanat kjanat removed invalid This doesn't seem right tech debt Known compromise or shortcut to revisit later labels Jun 28, 2026
@kjanat kjanat changed the title fix(resolve): bias same-name selection to any forced PM's own source fix(resolve): forced PM biases task selection Jun 28, 2026
@kjanat kjanat added the cr:review Allow CodeRabbit review label Jun 28, 2026
@kjanat kjanat merged commit e4307af into master Jun 28, 2026
18 checks passed
@kjanat kjanat deleted the fix/pm-override-task-source-bias branch June 28, 2026 02:44
kjanat added a commit that referenced this pull request Jun 28, 2026
The #75 branch forked before #71 and #73 merged, so its CHANGELOG was missing
their Unreleased entries. Three-way merged master's CHANGELOG.md (per-task
chain timing in Added, forced-PM source bias in Fixed) on top of the branch's
`run <path>` entry. The changelog now diverges from master by only this PR's
entry, so it merges cleanly when #75 lands.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: cli Argument parsing, commands, and CLI UX bug Something isn't working cr:review Allow CodeRabbit review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(resolve): --pm deno/RUNNER_PM=deno should pick deno's task on name conflicts, not run the package.json script through deno

1 participant