fix(FN-4482): harden scope guard typing and prompt source
Fusion-Task-Id: FN-4482 Fusion-Task-Lineage: 5850cda2-ded5-42ac-a206-396839580748
This commit is contained in:
@@ -91,7 +91,7 @@ describe("FN-4482 plan-only scope leak guard", () => {
|
||||
const { store, tool } = await setup({ unstaged: ["docs/foo.md"] });
|
||||
const result = await tool.execute("id", {});
|
||||
expect(result.content[0].text).toContain("Task marked complete");
|
||||
expect(store.logEntry.mock.calls.some(([_, message]) => String(message).includes("[scope-leak] reviewLevel="))).toBe(false);
|
||||
expect(store.logEntry.mock.calls.some((call: unknown[]) => String(call[1]).includes("[scope-leak] reviewLevel="))).toBe(false);
|
||||
});
|
||||
|
||||
it("warns but allows plan-only off-scope edits in default warn mode", async () => {
|
||||
@@ -131,7 +131,7 @@ describe("FN-4482 plan-only scope leak guard", () => {
|
||||
const { store, tool } = await setup({ enforcement: "off", unstaged: ["packages/core/src/db.ts"] });
|
||||
const result = await tool.execute("id", {});
|
||||
expect(result.content[0].text).toContain("Task marked complete");
|
||||
expect(store.logEntry.mock.calls.some(([_, message]) => String(message).includes("[scope-leak] reviewLevel="))).toBe(false);
|
||||
expect(store.logEntry.mock.calls.some((call: unknown[]) => String(call[1]).includes("[scope-leak] reviewLevel="))).toBe(false);
|
||||
});
|
||||
|
||||
it.each([0, 2])("uses warn-only behavior for non-plan-only review level %s", async (reviewLevel) => {
|
||||
@@ -151,6 +151,6 @@ describe("FN-4482 plan-only scope leak guard", () => {
|
||||
const result = await tool.execute("id", {});
|
||||
expect(result.content[0].text).toContain("Task marked complete");
|
||||
expect((executorLog.warn as any).mock.calls.some(([message]: [string]) => message.includes("Failed to capture uncommitted modified files"))).toBe(true);
|
||||
expect(store.logEntry.mock.calls.some(([_, message]) => String(message).includes("[scope-leak] reviewLevel="))).toBe(false);
|
||||
expect(store.logEntry.mock.calls.some((call: unknown[]) => String(call[1]).includes("[scope-leak] reviewLevel="))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3266,7 +3266,7 @@ export class TaskExecutor {
|
||||
this.createTaskLogTool(task.id),
|
||||
this.createTaskCreateTool(),
|
||||
this.createTaskAddDepTool(task.id),
|
||||
this.createTaskDoneTool(task.id, worktreePath, () => { taskDone = true; }),
|
||||
this.createTaskDoneTool(task.id, worktreePath, detail.prompt ?? "", () => { taskDone = true; }),
|
||||
createRunVerificationTool({
|
||||
worktreePath,
|
||||
rootDir: this.rootDir,
|
||||
@@ -4875,6 +4875,7 @@ export class TaskExecutor {
|
||||
private async evaluateTaskDoneScopeLeak(
|
||||
task: Task,
|
||||
worktreePath: string,
|
||||
promptContent: string,
|
||||
settings: Settings,
|
||||
): Promise<{ blocked: false } | { blocked: true; message: string }> {
|
||||
if (task.scopeOverride === true) {
|
||||
@@ -4888,7 +4889,7 @@ export class TaskExecutor {
|
||||
return { blocked: false };
|
||||
}
|
||||
|
||||
const reviewLevel = parseReviewLevelFromPrompt(task.prompt ?? "");
|
||||
const reviewLevel = parseReviewLevelFromPrompt(promptContent);
|
||||
const configuredMode = settings.planOnlyScopeLeakEnforcement ?? "warn";
|
||||
const enforcementMode: "off" | "warn" | "block" = reviewLevel === 1
|
||||
? configuredMode
|
||||
@@ -4928,7 +4929,7 @@ export class TaskExecutor {
|
||||
return { blocked: false };
|
||||
}
|
||||
|
||||
private createTaskDoneTool(taskId: string, worktreePath: string, onDone: () => void): ToolDefinition {
|
||||
private createTaskDoneTool(taskId: string, worktreePath: string, promptContent: string, onDone: () => void): ToolDefinition {
|
||||
const store = this.store;
|
||||
return {
|
||||
name: "fn_task_done",
|
||||
@@ -5008,7 +5009,7 @@ export class TaskExecutor {
|
||||
}
|
||||
|
||||
const settings = await store.getSettings();
|
||||
const scopeLeakCheck = await this.evaluateTaskDoneScopeLeak(task, worktreePath, settings)
|
||||
const scopeLeakCheck = await this.evaluateTaskDoneScopeLeak(task, worktreePath, promptContent, settings)
|
||||
.catch((error: unknown) => {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
executorLog.warn(`${taskId}: scope-leak guard failed open: ${errorMessage}`);
|
||||
|
||||
Reference in New Issue
Block a user