feat(miner): wire ContributionProfile eligibility filtering into discover - #7024
Conversation
…over (JSONbored#6798) Closes the AMS contribution-profile loop: discover now filters candidate issues through each target repo's ContributionProfile before ranking/enqueueing, so it no longer surfaces work a repo's own conventions would reject. The decision is a pure module (contribution-profile-filter.js): partitions candidates into kept + excluded-with-reason (exclusion_label / missing_eligibility_label / conflicting_signals). Conflicting signals resolve conservatively -- exclusion wins. SAFE DEFAULT: filtering activates for a repo only when its profile has an explicit eligibility signal; a low-confidence/empty/absent profile keeps every candidate, so real work is never silently skipped on a repo AMS couldn't read. The default resolver does no profile work at all without a github token, keeping the unauthenticated path byte-identical. The excluded set (repo/issue/reason) is surfaced in --json and plain-text output. Assignee exclusion is a documented follow-up (candidates don't carry assignees yet). Doc updated. Closes JSONbored#6798
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #7024 +/- ##
=======================================
Coverage 93.73% 93.73%
=======================================
Files 690 691 +1
Lines 68591 68654 +63
Branches 18748 18748
=======================================
+ Hits 64293 64356 +63
Misses 3302 3302
Partials 996 996
Flags with carried forward coverage won't be shown. Click here to find out more.
|
|
Tip ✅ LoopOver review result - approve/merge recommendedReview updated: 2026-07-17 18:45:43 UTC
Review summary Nits — 5 non-blocking
Decision drivers
Context & advisory signals — never blocks the verdict
Review context
Contributor next steps
Signal definitions
🧪 Chat with LoopOverAsk LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.
Full command reference: https://loopover.ai/docs/loopover-commands 🧪 Experimental — new and may change. 🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.
|
Closes JSONbored#7040, a follow-up to JSONbored#6798/JSONbored#7024: that PR candidly scoped out assignee-exclusion (candidate objects flowing through discover carried label names but not assignees, and threading them through was flagged as "a genuine second shared-file change"). This does that threading and adds the check. Extends RawCandidateIssue with `assignees` (already present in the same GitHub list/search payload discover already fetches -- no extra request), and adds a new `excluded_assignee` reason to contribution-profile-filter.js for a candidate assigned to its own repo's owner login. Unlike the three label-based rules, this check is structural and always-on -- per the schema (ContributionAssigneeRuntimeCheck), assignee-exclusion is deliberately not a profile field, so it runs ahead of (and independent of) the existing eligibilityLabels.confidence === "explicit" safe-default gate that the label rules are conditioned on. It applies regardless of whether the repo has a trustworthy ContributionProfile at all.
Summary
The final link of the AMS contribution-profile epic (#6793) — it closes the loop.
loopover-miner discovernow filters candidate issues through each target repo'sContributionProfile(the schema/#6795, extraction/#6796, cache/#6797 I landed earlier) before ranking and enqueueing, so it no longer surfaces work a repo's own conventions would reject. Depends on #6796 + #6797, both merged.The decision lives in a pure module
filterCandidatesByProfiles(contribution-profile-filter.js) partitions candidates intokept+excluded, each excluded entry carrying a reason. Keeping the decision pure and isolated means it's a new file with 100% coverage and zero contention;discover-cli.jsonly resolves the profiles and renders the result.Reasons:
exclusion_label,missing_eligibility_label,conflicting_signals. Matching is against the candidate's own label names, using the eligibility/exclusion label names the profile recorded inprovenance(case-insensitive).The two behaviors the issue calls out
Safe default — the load-bearing rule. Filtering activates for a repo only when its profile has an explicit eligibility signal (
eligibilityLabels.confidence === "explicit"). A repo with no profile, or a low-confidence/empty one — one whose conventions AMS simply couldn't read — keeps every candidate. A weak profile can never silently skip real, eligible work. Second layer: the default resolver does no profile work at all without a github token (it can't read a taxonomy reliably unauthenticated), so the unauthenticated CLI path — and every existing test — is byte-identical to before.Conflicting signals resolve conservatively. An issue carrying both an eligibility and an exclusion label is excluded (
conflicting_signals) — a maintainer marking it off-limits outranks its also being help-wanted. Documented and tested.Scope note — assignees
The issue lists "excluded assignee" as one fail reason, but the candidate objects flowing through
discovercarry label names and not assignees (opportunity-fanout.js'snormalizeIssuedrops them), andContributionAssigneeRuntimeCheckis deliberately a runtime concern, not a profile field. Threading assignees through the fan-out is a genuine second shared-file change, so this PR scopes filtering to labels — the primary eligibility signal the #6794 inventory found — and documents assignee-exclusion as a follow-up rather than half-wiring it. Flagging that honestly rather than silently dropping a listed requirement.Tests
The three cases the issue requires, plus the safe-default and both output surfaces:
conflicting_signals.--jsonand plain-text output, the same filter on--dry-run(nothing enqueued), and the default resolver's no-token/cache-hit/cache-miss paths.Validation
contribution-profile-filter.js(34/34 statements, 22/22 branches) and the changed lines indiscover-cli.js— zero uncovered, zero partial.npm run typecheck— 0 (after the standard engine rebuild; the new.d.tsdeclares the option + export).eslint— 0/0 ·prettier --check— clean ·node --check(added to the miner build list) — ok · rebased on latestmain.One pre-existing failure, not mine:
miner-status.test.ts's "resolves the state dir…" fails identically on cleanmain(Windows\vs/); it's not in this PR's diff and passes in CI's Linux.Scope & safety
.d.ts, thediscover-cli.jswiring + its.d.ts, the build-list entry, the design-doc update, and two test files. Wanted paths (packages/,test/).src/api/routes.ts,src/mcp/server.ts, the MCP CLI bin, or the stdio-tool count pin.site//CNAME/lovablechanges.Closes #6798