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:
committed by
gsxdsm
parent
1a4525e0b9
commit
37d4115bb6
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user