Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions src/github/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -906,7 +906,7 @@ function duplicateCheckSections(bundle: AgentRunBundle | null | undefined): stri
lines.push(`- ${publicBlockerLabel(code)}`);
}
const caution = [...action.why, action.riskImpact ?? ""]
.filter((item) => item.trim().length > 0 && (mentionsDuplicateRiskText(item) || /\blikely_duplicate\b/i.test(item)))
.filter(isPublicDuplicateCautionLine)
.slice(0, 3)
.map((item) => `- ${publicBlockerDetail(item)}`);
lines.push(...caution);
Expand Down Expand Up @@ -1271,15 +1271,22 @@ function formatActionBullets(
}

function mentionsDuplicateRisk(action: AgentActionRecord): boolean {
return [action.publicSafeSummary, action.recommendation, action.riskImpact ?? "", ...action.why, ...action.blockedBy].some((item) =>
mentionsDuplicateRiskText(item),
);
return [action.publicSafeSummary, action.recommendation, action.riskImpact ?? "", ...action.why, ...action.blockedBy].some(isPublicDuplicateCautionLine);
}

function mentionsDuplicateRiskText(value: string): boolean {
return /\b(duplicate|overlap|wip|collision|concurrent|in[- ]progress)\b/i.test(value);
}

function isPublicDuplicateCautionLine(value: string): boolean {
const detail = value.trim();
return detail.length > 0 && !mentionsRepoOutcomePatternDetail(detail) && (mentionsDuplicateRiskText(detail) || /\blikely_duplicate\b/i.test(detail));
}

function mentionsRepoOutcomePatternDetail(value: string): boolean {
return /\bPRs (?:touching|labeled|with|that|from) .+\b(?:merge well|high closure risk) here \(\d+\/\d+ merged\)\./i.test(value);
}

function publicBlockerLabel(code: string): string {
const normalized = code.trim().toLowerCase();
const labels: Record<string, string> = {
Expand Down
6 changes: 2 additions & 4 deletions src/services/decision-pack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -737,8 +737,6 @@ function buildRepoDecision(args: {
const manifestSummary = manifest && manifest.present ? buildRepoDecisionManifestSummary(manifest) : undefined;
const manifestReasons = manifest && manifest.present ? buildRepoDecisionManifestReasons(manifest) : { whyThisHelps: [], nextActions: [], publicNextActions: [], riskReasons: [] };
const repoOutcomePatterns = summarizeRepoOutcomePatterns(args.repoOutcomePatterns);
const outcomeRiskLines = args.roleContext.maintainerLane ? [] : (repoOutcomePatterns?.riskPatterns ?? []).slice(0, 2).map((pattern) => pattern.detail);
const outcomeSuccessLines = recommendation === "pursue" ? (repoOutcomePatterns?.successPatterns ?? []).slice(0, 1).map((pattern) => pattern.detail) : [];
const recommendationFeedbackRiskLines = args.roleContext.maintainerLane ? [] : recommendationFeedbackRiskReasons(recommendationFeedback);
const recommendationFeedbackSuccessLines = recommendationFeedbackWhyThisHelps(recommendationFeedback);
const tradeoffSummary = buildRepoDecisionTradeoffSummary({
Expand All @@ -751,8 +749,8 @@ function buildRepoDecision(args: {
manifestSummary,
blockers,
});
const finalRiskReasons = [...new Set([...riskReasons, ...manifestReasons.riskReasons, ...outcomeRiskLines, ...recommendationFeedbackRiskLines])];
const finalWhyThisHelps = [...new Set([...whyThisHelpsFor(recommendation, copyContext), ...manifestReasons.whyThisHelps, ...outcomeSuccessLines, ...recommendationFeedbackSuccessLines])];
const finalRiskReasons = [...new Set([...riskReasons, ...manifestReasons.riskReasons, ...recommendationFeedbackRiskLines])];
const finalWhyThisHelps = [...new Set([...whyThisHelpsFor(recommendation, copyContext), ...manifestReasons.whyThisHelps, ...recommendationFeedbackSuccessLines])];
const finalNextActions = [...new Set([...nextActionsFor(recommendation, copyContext), ...manifestReasons.nextActions])];
const finalPublicNextActions = [...new Set([...publicNextActionsFor(recommendation, copyContext), ...manifestReasons.publicNextActions])];
const counterfactualReasons = buildRepoDecisionCounterfactualReasons({
Expand Down
10 changes: 6 additions & 4 deletions test/unit/decision-pack.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ describe("decision-pack service", () => {
expect(negative.directPrShare).toBeCloseTo(0.01 * 0.9 * 1, 10); // 0.009
});

it("feeds repo outcome patterns into repo decisions without inflating maintainer-lane evidence", () => {
it("keeps repo outcome patterns scoped to the private pattern field", () => {
const outsideRole = { maintainerLane: false } as any;
const maintainerRole = { maintainerLane: true } as any;
const patterns = {
Expand All @@ -178,10 +178,12 @@ describe("decision-pack service", () => {
});
expect(pursue.recommendation).toBe("pursue");
expect(pursue.repoOutcomePatterns?.sampleSize).toBe(8);
expect(pursue.whyThisHelps.some((line) => line.includes("PRs touching src/ merge well here"))).toBe(true);
expect(pursue.riskReasons.some((line) => line.includes("high closure risk"))).toBe(true);
expect(pursue.repoOutcomePatterns?.successPatterns[0]?.detail).toContain("PRs touching src/ merge well here");
expect(pursue.repoOutcomePatterns?.riskPatterns[0]?.detail).toContain("high closure risk");
expect(pursue.whyThisHelps.some((line) => line.includes("PRs touching src/ merge well here"))).toBe(false);
expect(pursue.riskReasons.some((line) => line.includes("high closure risk"))).toBe(false);

// Maintainer-lane repos surface the patterns for context but never fold the risk into the contributor's own risk reasons.
// Maintainer-lane repos also surface the patterns for private context without folding risk into generic reasons.
const maintainer = __decisionPackInternals.buildRepoDecision({
repo: repo("owner/direct", 0.03, 0),
roleContext: maintainerRole,
Expand Down
40 changes: 40 additions & 0 deletions test/unit/github-commands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,46 @@ describe("GitHub mention commands", () => {
expect(body).not.toMatch(/wallet|hotkey|raw trust score|payout|reward estimate|farming|private reviewability|public score estimate/i);
});

it("does not publish repo outcome-pattern details in duplicate-check comments", () => {
const body = buildPublicAgentCommandComment({
command: parseGittensoryMentionCommand("@gittensory duplicate-check")!,
repo: null,
issue: { number: 99, title: "PR", state: "open", pull_request: {} },
pullRequest: null,
actorKind: "maintainer",
bundle: {
run: completedRun("run-duplicate-outcome-pattern"),
actions: [
{
id: "repo-outcome-pattern-action",
runId: "run-duplicate-outcome-pattern",
actionType: "check_duplicate_risk" as const,
status: "recommended" as const,
recommendation: "Open direct PR",
why: [
"PRs touching duplicate/ have high closure risk here (0/3 merged).",
'PRs labeled "wip" merge well here (3/3 merged).',
],
blockedBy: [],
riskImpact: "PRs touching collision/ have high closure risk here (0/3 merged).",
publicSafeSummary: "Consider a narrow public-safe change.",
approvalRequired: true,
safetyClass: "private" as const,
payload: {},
},
],
contextSnapshots: [],
summary: "duplicate outcome-pattern guard",
},
});

expect(body).toContain("**Duplicate & WIP caution**");
expect(body).toContain("Consider a narrow public-safe change.");
expect(body).not.toContain("PRs touching duplicate/");
expect(body).not.toContain("high closure risk here (0/3 merged)");
expect(body).not.toContain("merge well here (3/3 merged)");
});

it("renders command-specific sections for preflight, blockers, duplicate-check, and next-action", () => {
const bundle = sampleBundle();

Expand Down