feat(FN-5231): detect and flag broad-scope tasks at triage to prompt scope

- docs(FN-5231): complete Step 6 — AGENTS.md + delivery summary + dashboard follow-up
- test(FN-5231): complete Step 5 — verification gates green
- test(FN-5231): complete Step 4 — reliability interaction regression for broad-scope flag
- feat(FN-5231): complete Step 3 — wire broad-scope flag into finalizeApprovedTask
- feat(FN-5231): complete Step 2 — register broad-scope audit event type
- feat(FN-5231): complete Step 1 — broad-scope heuristic helper

Fusion-Task-Id: FN-5231
This commit is contained in:
Fusion (runfusion.ai)
2026-05-20 05:28:36 -07:00
committed by gsxdsm
parent 1a4525e0b9
commit 37d4115bb6
6 changed files with 520 additions and 1 deletions

View File

@@ -74,6 +74,11 @@ import {
isResearchToolSurfaceEnabled,
} from "./tool-availability.js";
import { runGhostBugPreflight } from "./triage-preflight.js";
import {
BROAD_SCOPE_FLAG_VERSION,
decideBroadScopeFlag,
extractBroadScopeSignals,
} from "./triage-broad-scope-heuristics.js";
import { archiveAsGhostBug } from "./self-healing.js";
import { createRunAuditor, generateSyntheticRunId } from "./run-audit.js";
@@ -2309,7 +2314,63 @@ export class TriageProcessor {
taskUpdates.noCommitsExpected = true;
}
const parsedFileScope = parseFileScopeFromPrompt(written);
let parsedFileScope = parseFileScopeFromPrompt(written);
try {
const persistedFileScope = await this.store.parseFileScopeFromPrompt(task.id);
if (persistedFileScope.length > parsedFileScope.length) {
parsedFileScope = persistedFileScope;
}
} catch {
// Fail open on persisted PROMPT.md parsing and keep using the in-memory parse.
}
type BroadScopeFlagRecord = {
score: number;
reasons: string[];
signals: {
size: "S" | "M" | "L" | null;
stepCount: number;
fileScopeCount: number;
failingFileMentions: number;
};
thresholds: {
stepsHigh: number;
fileScopeHigh: number;
failingFileMentionsHigh: number;
sizeLStepsThreshold: number;
};
version: number;
flaggedAt: string;
};
let broadScopeFlagRecord: BroadScopeFlagRecord | null = null;
try {
const broadScopeSignals = extractBroadScopeSignals({
size: taskUpdates.size ?? task.size ?? null,
stepCount: parsedSteps.length,
fileScopeCount: parsedFileScope.length,
descriptionText: task.description ?? "",
});
const broadScopeDecision = decideBroadScopeFlag(broadScopeSignals);
if (broadScopeDecision.flagged) {
broadScopeFlagRecord = {
score: broadScopeDecision.score,
reasons: broadScopeDecision.reasons,
signals: broadScopeDecision.signals,
thresholds: broadScopeDecision.thresholds,
version: BROAD_SCOPE_FLAG_VERSION,
flaggedAt: new Date().toISOString(),
};
taskUpdates.sourceMetadataPatch = {
...(taskUpdates.sourceMetadataPatch ?? {}),
broadScopeFlag: broadScopeFlagRecord,
};
planLog.warn(
`${task.id}: broad-scope flag at triage — score=${broadScopeDecision.score}, reasons=${broadScopeDecision.reasons.join(",")}`,
);
}
} catch (error: unknown) {
const message = error instanceof Error ? error.message : String(error);
planLog.warn(`${task.id}: broad-scope heuristic failed open: ${message}`);
}
let taskIntentSignature: ReturnType<typeof extractIntentSignature> = {
routePaths: [],
filePaths: [],
@@ -2346,6 +2407,37 @@ export class TriageProcessor {
await this.store.updateTask(task.id, taskUpdates);
if (broadScopeFlagRecord) {
try {
await this.store.logEntry(
task.id,
"Broad-scope triage flag",
`Heuristics suggest this task may benefit from decomposition (score=${broadScopeFlagRecord.score}; signals: ${broadScopeFlagRecord.reasons.join(", ")}). Consider creating child tasks via fn_task_create or marking breakIntoSubtasks=true before execution.`,
);
const auditor = createRunAuditor(this.store, {
taskId: task.id,
agentId: task.assignedAgentId ?? "triage",
runId: generateSyntheticRunId("triage", task.id),
phase: "triage",
source: "triage",
});
await auditor.database({
type: "task:broad-scope-flagged-at-triage",
target: task.id,
metadata: {
score: broadScopeFlagRecord.score,
reasons: broadScopeFlagRecord.reasons,
signals: broadScopeFlagRecord.signals,
thresholds: broadScopeFlagRecord.thresholds,
version: broadScopeFlagRecord.version,
},
});
} catch (error: unknown) {
const message = error instanceof Error ? error.message : String(error);
planLog.warn(`${task.id}: broad-scope heuristic failed open: ${message}`);
}
}
try {
const preflightDecision = await Promise.race([
runGhostBugPreflight(