Skip to content

Reward-risk next-actions are truncated *before* deduplication, dropping distinct steps #463

Description

@philluiz2323

Repo: JSONbored/gittensory
File: src/signals/reward-risk.ts (lines 274 and 372)
Severity: medium (correctness — contributor-facing guidance silently loses distinct actions)

Summary

Two nextActions aggregations put .slice(0, N) inside new Set(...), so
they truncate the flattened list to N entries and then deduplicate — yielding
fewer than N unique actions whenever the first N contain duplicates, while
genuinely-distinct actions further down the list are dropped even though there is
room for them. Every other dedup-and-cap in the codebase does the opposite order
(dedupe, then cap).

Evidence

// src/signals/reward-risk.ts:274  (per-repo)
const nextActions = [...new Set(actions.flatMap((action) => action.nextActions).slice(0, 8))];

// src/signals/reward-risk.ts:372  (cross-repo aggregate)
const nextActions = [...new Set(topActions.flatMap((action) => action.nextActions).slice(0, 10))];

The .slice() is applied to the flattened array before it is passed to
new Set(...), so the cap is consumed by duplicates.

Inconsistent with the codebase's own convention

Every sibling dedups first, then caps — including the identical
flatMap-nextActions-dedup-cap in decision-pack:

  • src/services/decision-pack.ts:619[...new Set([...monitorNextSteps, ...topActions.flatMap((a) => a.nextActions)])].slice(0, 12)
  • src/queue/processors.ts:342,354[...new Set(...flatMap(...))].slice(0, 200/500)
  • src/services/agent-orchestrator.ts:616, src/services/queue-trends.ts:126,
    src/signals/local-branch.ts:1067,1113, src/services/agent-action-explanation-card.ts:102 — all [...new Set(...)].slice(0, N)

reward-risk.ts:274 and :372 are the only two sites that slice inside the Set.

Concrete trace (line 372)

topActions is the cross-repo aggregate
(repoAnalyses.flatMap((a) => a.actions) … .slice(0, 12)), and each action's
nextActions = nextActionsFor(kind) returns the same string for a given kind
regardless of repo
. So when the top repos share an action kind (e.g. the five
highest-priority repos all recommend open_new_direct_pr), the flattened list
starts with that identical string repeated:

["Only open a new PR after …",  // repo A (open_new_direct_pr)
 "Only open a new PR after …",  // repo B (open_new_direct_pr)
 "Only open a new PR after …",  // repo C
 "Land, close, or withdraw …", "Prioritize the repo where …",  // repo D (cleanup_existing_prs)
 "Only open a new PR after …",  // repo E
 …
 "File only high-proof issues …",      // repo H (file_issue_discovery)  ← position 11+
 "Improve labels, contribution docs …" // repo I (maintainer_lane)       ← position 12+
]
  • Current: slice(0, 10) keeps the first 10 (mostly the repeated
    open_new_direct_pr string) → new Set collapses them to ~3 unique →
    file_issue_discovery / maintainer_lane steps at positions 11+ are dropped.
  • Intended: [...new Set(flat)].slice(0, 10) collapses the repeats first,
    then keeps up to 10 distinct steps — surfacing the issue-discovery and
    maintainer-lane actions.

So the contributor's top-level strategy nextActions shows a handful of
duplicated-then-deduped steps and silently omits distinct, actionable guidance
that would fit within the cap.

Test status

No test locks in the current behavior — there is no reward-risk /
reviewability unit test file, and no test asserts nextActions length or
contents.

Suggested fix

Move the cap outside the Set, matching the rest of the codebase:

// line 274
const nextActions = [...new Set(actions.flatMap((action) => action.nextActions))].slice(0, 8);
// line 372
const nextActions = [...new Set(topActions.flatMap((action) => action.nextActions))].slice(0, 10);

Add a fixture where several top repos share an action kind and assert the
aggregate nextActions still surfaces the distinct lower-priority steps.

Metadata

Metadata

Assignees

No one assigned

    Labels

    slopAI slop and/or attempts to game additional points via manipulation or alt profiles.

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions