diff --git a/src/github/commands.ts b/src/github/commands.ts index c20bf52f..aa6eba1a 100644 --- a/src/github/commands.ts +++ b/src/github/commands.ts @@ -744,6 +744,8 @@ function askSourcesFromContextSnapshot(snapshot: AgentRunBundle["contextSnapshot return sources; } +const PRIVATE_ASK_ACTION_EVIDENCE_SOURCES = new Set(["repo_decision"]); + function askSourcesFromActionEvidence(action: AgentActionRecord): AskContributingSource[] { const evidence = readRecord(action.payload.recommendationEvidence); if (!evidence || !Array.isArray(evidence.sources)) return []; @@ -752,18 +754,30 @@ function askSourcesFromActionEvidence(action: AgentActionRecord): AskContributin if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null; const source = raw as Record; const name = typeof source.name === "string" ? source.name : "connected_source"; + if (PRIVATE_ASK_ACTION_EVIDENCE_SOURCES.has(name)) return null; return { key: name, label: askSourceLabel(name), origin: name, generatedAt: typeof source.generatedAt === "string" ? source.generatedAt : null, freshness: typeof source.freshness === "string" ? source.freshness : "unknown", - detail: typeof source.summary === "string" ? source.summary : "Connected recommendation evidence source.", + detail: publicActionEvidenceSourceDetail(name), }; }) .filter((entry): entry is AskContributingSource => entry !== null); } +function publicActionEvidenceSourceDetail(name: string): string { + const details: Record = { + contributor_decision_pack: "Contributor decision-pack metadata was available for this cached agent run.", + official_contributor_stats: "Official contributor statistics metadata was available for this cached agent run.", + repo_outcome_history: "Repo outcome history metadata was available for this cached agent run.", + aggregate_outcome_quality: "Aggregate outcome-quality metadata was available for this cached agent run.", + open_pr_monitor: "Cached open PR and issue queue metadata was available for this cached agent run.", + }; + return details[name] ?? "Connected recommendation evidence metadata was available for this cached agent run."; +} + function formatAskCitation(source: AskContributingSource): string { const header = `Source: ${source.label}; origin: ${source.origin}; freshness: ${source.freshness}`; const observed = source.generatedAt ? ` as of ${source.generatedAt}` : ""; diff --git a/test/unit/github-commands.test.ts b/test/unit/github-commands.test.ts index 07db76a1..496f19ac 100644 --- a/test/unit/github-commands.test.ts +++ b/test/unit/github-commands.test.ts @@ -1549,6 +1549,68 @@ describe("ask citation helpers", () => { expect(sections.join("\n")).toContain("Try @gittensory ask again shortly"); }); + it("does not publish private repo decision ranking evidence in ask citations", () => { + const bundle = askCitedBundle({ + actions: [ + { + id: "ask-private-decision-action", + runId: "run-ask-cited", + actionType: "choose_next_work", + status: "recommended", + recommendation: "recommendation", + why: [], + blockedBy: [], + targetRepoFullName: "owner/private-repo", + publicSafeSummary: "Run local branch preflight first.", + approvalRequired: true, + safetyClass: "private", + payload: { + recommendationEvidence: { + confidence: "high", + sourceSummary: "Decision pack evidence", + freshness: "fresh", + sources: [ + { + name: "repo_decision", + source: "decision_pack", + generatedAt: "2026-06-01T12:00:00.000Z", + freshness: "fresh", + summary: "owner/private-repo ranked pursue_now at priority 0.87321.", + }, + { + name: "open_pr_monitor", + source: "github_cache", + generatedAt: "2026-06-01T12:00:00.000Z", + freshness: "fresh", + summary: "Open PR monitor queue metadata.", + }, + ], + }, + }, + }, + ], + contextSnapshots: [], + }); + + const sources = githubCommandsInternals.collectAskContributingSources(bundle); + expect(sources.some((source) => source.origin === "repo_decision")).toBe(false); + expect(sources.find((source) => source.origin === "open_pr_monitor")?.detail).toBe( + "Cached open PR and issue queue metadata was available for this cached agent run.", + ); + + const comment = buildPublicAgentCommandComment({ + command: parseGittensoryMentionCommand("@gittensory ask what should I do next?")!, + repo: null, + issue: { number: 44, title: "PR", state: "open", pull_request: {} }, + pullRequest: null, + actorKind: "author", + bundle, + }); + expect(comment).toContain("origin: open_pr_monitor"); + expect(comment).not.toMatch(/ranked|pursue_now|priority 0\.87321/i); + expect(comment).not.toContain("owner/private-repo ranked"); + }); + it("collects connected-source metadata and formats concrete citations", () => { const sources = githubCommandsInternals.collectAskContributingSources({ run: completedRun("run-ask-internals"),