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
4 changes: 3 additions & 1 deletion packages/loopover-miner/lib/idea-feasibility-cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ export function parseIdeaFeasibilityArgs(args) {
}
if (token === "--hint") {
const value = args[i + 1];
if (value === undefined || value.startsWith("-")) {
// A whitespace-only hint is as empty as a missing one (#6766): it declares no testable success signal, so
// it gets the same rejection rather than sailing through as a real objective signal.
if (value === undefined || value.startsWith("-") || value.trim() === "") {
return { error: "--hint requires a value." };
}
options.acceptanceHints.push(value);
Expand Down
6 changes: 5 additions & 1 deletion packages/loopover-miner/lib/idea-feasibility.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ export function deriveIdeaIssueStatus(idea, resolved) {
// Out of the loop's scope: the idea does not resolve to a repo the loop can act on.
if (!resolved.targetResolvable) return "missing";
// Impossible to evaluate objectively: no declared success signal, so the loop could never test its own output.
const objectiveSignals = idea.acceptanceHints?.length ?? 0;
// Count CONTENT, not array length (#6766): a blank/whitespace-only hint declares nothing testable, so it must
// not pass as an objective signal just by occupying a slot.
const objectiveSignals = (idea.acceptanceHints ?? []).filter(
(hint) => typeof hint === "string" && hint.trim() !== "",
).length;
if (objectiveSignals === 0) return "invalid";
return "ready";
}
Expand Down
9 changes: 9 additions & 0 deletions test/unit/miner-idea-feasibility-cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,15 @@ describe("parseIdeaFeasibilityArgs (#5671)", () => {
});
});

it("REGRESSION (#6766): rejects a whitespace-only --hint the same way as a missing one", () => {
// A blank hint declares no testable success signal, so it must not sail through as a real one.
for (const blank of [" ", "\t", " \n "]) {
expect(parseIdeaFeasibilityArgs(["unclaimed", "none", "--hint", blank])).toEqual({
error: "--hint requires a value.",
});
}
});

it("rejects --hint whose value looks like another flag", () => {
expect(parseIdeaFeasibilityArgs(["unclaimed", "none", "--hint", "--json"])).toEqual({
error: "--hint requires a value.",
Expand Down
11 changes: 11 additions & 0 deletions test/unit/miner-idea-feasibility.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ describe("deriveIdeaIssueStatus (#5671)", () => {
expect(deriveIdeaIssueStatus({ acceptanceHints: [] }, { targetResolvable: true })).toBe("invalid");
});

it("REGRESSION (#6766): returns 'invalid' for blank/whitespace-only hints — a slot is not a signal", () => {
// The count used to be array LENGTH, so a whitespace-only hint declared nothing testable yet passed as
// "ready", contradicting the module's own "no objective success signal is invalid" contract.
expect(deriveIdeaIssueStatus({ acceptanceHints: [" "] }, { targetResolvable: true })).toBe("invalid");
expect(deriveIdeaIssueStatus({ acceptanceHints: ["", "\t", "\n "] }, { targetResolvable: true })).toBe("invalid");
});

it("REGRESSION (#6766): a real hint alongside blank ones still counts as an objective signal", () => {
expect(deriveIdeaIssueStatus({ acceptanceHints: [" ", "retries on 5xx"] }, { targetResolvable: true })).toBe("ready");
});

it("returns 'ready' when the idea resolves and carries at least one objective success signal", () => {
expect(deriveIdeaIssueStatus({ acceptanceHints: ["uploads retry on 5xx"] }, { targetResolvable: true })).toBe("ready");
});
Expand Down