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
18 changes: 16 additions & 2 deletions src/signals/focus-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -685,11 +685,16 @@ export function buildFocusManifestGuidance(args: {
}

if (manifest.wantedPaths.length > 0 && matchedWantedPaths.length === 0 && changedPaths.length > 0) {
// Public-safety filter before interpolation (#5945) -- mirrors safeExpectations below. manifest.wantedPaths
// is maintainer-authored and never public-safety-checked upstream; without this, a public-unsafe pattern
// would leak verbatim into a contributor-facing finding.
const safeWantedPaths = manifest.wantedPaths.filter(isFocusManifestPublicSafe).slice(0, 5);
const wantedPathsDetail = safeWantedPaths.length > 0 ? ` (${safeWantedPaths.join(", ")})` : "";
findings.push({
code: "manifest_off_focus",
severity: "warning",
title: "Change is outside maintainer-wanted areas",
detail: `No changed path matches the maintainer-wanted patterns (${manifest.wantedPaths.slice(0, 5).join(", ")}).`,
detail: `No changed path matches the maintainer-wanted patterns${wantedPathsDetail}.`,
action: "Refocus the change onto a maintainer-wanted area or explain why this out-of-focus work is needed.",
});
publicNextSteps.push("Refocus onto the maintainer-wanted areas, or explain why this out-of-focus change is needed.");
Expand All @@ -706,11 +711,20 @@ export function buildFocusManifestGuidance(args: {
}

if (manifest.preferredLabels.length > 0 && preferredLabelHits.length === 0) {
// Public-safety filter before interpolation (#5945) -- mirrors safeExpectations below. Unlike
// manifest_off_focus, this finding's ENTIRE detail is built from the label list, so a zero-safe-entries
// fallback needs its own sentence, not just a dropped parenthetical -- the title text already says the
// same thing and is a static, always-public-safe string.
const safePreferredLabels = manifest.preferredLabels.filter(isFocusManifestPublicSafe).slice(0, 5);
const preferredLabelsDetail =
safePreferredLabels.length > 0
? `Maintainer prefers labels: ${safePreferredLabels.join(", ")}.`
: "No maintainer-preferred label applied.";
findings.push({
code: "manifest_missing_preferred_label",
severity: "info",
title: "No maintainer-preferred label applied",
detail: `Maintainer prefers labels: ${manifest.preferredLabels.slice(0, 5).join(", ")}.`,
detail: preferredLabelsDetail,
action: "Consider applying a maintainer-preferred label so triage stays aligned.",
});
publicNextSteps.push(`Consider a maintainer-preferred label (${manifest.preferredLabels.slice(0, 3).join(", ")}).`);
Expand Down
22 changes: 22 additions & 0 deletions test/unit/focus-manifest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,28 @@ describe("buildFocusManifestGuidance", () => {
expect(missingTests?.detail).not.toContain("wallet");
});

it("filters public-unsafe wantedPaths/preferredLabels entries out of finding details (#5945)", () => {
// manifest_off_focus and manifest_missing_preferred_label interpolate wantedPaths/preferredLabels
// directly into contributor-facing detail text -- both are maintainer-authored and must be filtered
// through isFocusManifestPublicSafe before interpolation, same as testExpectations above (#3304).
const unsafeManifest = parseFocusManifest({ wantedPaths: ["wallet-onboarding/"], preferredLabels: ["reward-tracking"] });
const guidance = buildFocusManifestGuidance({
manifest: unsafeManifest,
changedPaths: ["src/x.ts"],
labels: [],
linkedIssueCount: 1,
testFileCount: 1,
});

const offFocus = guidance.findings.find((finding) => finding.code === "manifest_off_focus");
expect(offFocus?.detail).toBe("No changed path matches the maintainer-wanted patterns.");
expect(offFocus?.detail).not.toContain("wallet");

const missingLabel = guidance.findings.find((finding) => finding.code === "manifest_missing_preferred_label");
expect(missingLabel?.detail).toBe("No maintainer-preferred label applied.");
expect(missingLabel?.detail).not.toContain("reward");
});

it("treats passing validation as satisfying test expectations", () => {
const guidance = buildFocusManifestGuidance({ manifest: wanted, changedPaths: ["src/x.ts"], linkedIssueCount: 1, testFileCount: 0, passedValidationCount: 2 });
expect(guidance.findings.some((finding) => finding.code === "manifest_missing_tests")).toBe(false);
Expand Down
34 changes: 34 additions & 0 deletions test/unit/local-branch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1660,6 +1660,40 @@ describe("local branch analysis", () => {
expect(JSON.stringify(analysis.prPacket)).not.toMatch(/reward|score|wallet|hotkey|farming|payout|ranking|trust score/i);
});

it("does not leak public-unsafe wantedPaths/preferredLabels through localFindings (#5945)", () => {
// manifestGuidance.findings is mapped verbatim into localFindings (the actual /v1 API + MCP
// exposure path), so a maintainer-authored public-unsafe wantedPaths/preferredLabels entry must
// already be filtered out of the finding detail upstream in buildFocusManifestGuidance -- this
// closes the contributor-facing exposure path, not just the guidance-builder unit above.
const analysis = buildLocalBranchAnalysis({
input: {
login: "oktofeesh1",
repoFullName: "entrius/allways-ui",
branchName: "fix-cache",
body: "Fixes #7",
changedFiles: [{ path: "src/cache.ts", additions: 4, deletions: 0, status: "modified" }],
focusManifest: {
source: "repo_file",
wantedPaths: ["wallet-onboarding/"],
preferredLabels: ["reward-tracking"],
},
},
repo,
issues: [{ repoFullName: repo.fullName, number: 7, title: "Cache refresh", state: "open", labels: [], linkedPrs: [] }],
pullRequests: [],
profile,
outcomeHistory,
scoringSnapshot,
scoringProfile,
});

const offFocus = analysis.localFindings.find((finding) => finding.code === "manifest_off_focus");
expect(offFocus?.detail).not.toContain("wallet");
const missingLabel = analysis.localFindings.find((finding) => finding.code === "manifest_missing_preferred_label");
expect(missingLabel?.detail).not.toContain("reward");
expect(JSON.stringify(analysis.localFindings)).not.toMatch(/wallet|reward/i);
});

it("ignores a malformed focus manifest without breaking analysis", () => {
const analysis = buildLocalBranchAnalysis({
input: {
Expand Down