fix(FN-352): address no-commit review feedback

This commit is contained in:
Phil Larson
2026-06-12 15:54:47 -07:00
parent 3a729f53a9
commit c6bcbe26a2
4 changed files with 52 additions and 4 deletions

View File

@@ -1,5 +1,5 @@
---
"@fusion/engine": patch
"@runfusion/fusion": patch
---
Classify harmless temporary merge worktree cleanup failures after `git worktree prune`/porcelain inspection while keeping still-registered worktree leaks visible in merger diagnostics.

View File

@@ -2368,6 +2368,8 @@ export interface Task {
sourceMessageId?: string;
sourceParentTaskId?: string;
sourceMetadata?: Record<string, unknown>;
/** Reconstructed task prompt content when available on in-memory execution tasks. */
prompt?: string;
/** Explicitly assigned user ID for task-user linking. Used during review handoff to indicate
* which user should review the task. The sentinel value "requesting-user" indicates the
* user who created or steered the task. */

View File

@@ -191,6 +191,48 @@ Atlas Notes task-board artifacts only:
expect(store.handoffToReview).not.toHaveBeenCalled();
});
it("FN-350 refuses contradictory implementation plus coordination fallback prompts", async () => {
const prompt = `# Task: FN-350 - Route Ready Swift Tasks to Executor Owner
## Review Level: 1 (Plan Only)
**Assessment:** This is a coordination/routing task that should not change product source.
## Mission
Implement the source fix if possible, or record the intentional block if no safe candidate exists. Do not change product source.
## File Scope
- FN-350 task document \`docs\` via \`fn_task_document_write\`
## Steps
### Step 1: Decide
- [x] Decision recorded.
`;
const { store, tool } = await setup({
id: "FN-350",
title: "Route Ready Swift Tasks to Executor Owner",
description: "Coordination/routing task with task-document evidence only.",
prompt,
branch: "fusion/fn-350",
noCommitsExpected: undefined,
steps: [{ name: "Decide", status: "done" as const }],
});
mockedExecSync.mockImplementation((cmd: string) => {
if (cmd.includes("rev-parse --show-toplevel")) return Buffer.from("/repo/.worktrees/swift-falcon\n");
if (cmd.includes("rev-parse --abbrev-ref HEAD")) return Buffer.from("fusion/fn-350\n");
if (cmd.includes("rev-list --count")) return Buffer.from("0\n");
if (cmd.includes("rev-parse HEAD")) return Buffer.from("def456\n");
return Buffer.from("");
});
const result = await tool.execute("id", {});
expect(result.content[0].text).toContain("fn_task_done refused: no_commits");
expect(store.moveTask).toHaveBeenCalledWith("FN-350", "todo", { preserveProgress: true });
});
it("FN-4114 still refuses source-changing implementation tasks with zero commits and no explicit no-commit contract", async () => {
const implementationPrompt = `# Task: FN-4114 - Implement source change

View File

@@ -257,7 +257,11 @@ function promptLooksCoordinationOnly(prompt: string): boolean {
const assessment = prompt.match(/^\*\*Assessment:\*\*\s*([^\n]+)/im)?.[1] ?? "";
const coordinationText = `${titleMatch}\n${mission}\n${assessment}`.toLowerCase();
const hasCoordinationIntent = /\b(coordination|routing|route|handoff|assign(?:ment)?|owner|triage|select exactly one|record (?:the )?intentional block)\b/.test(coordinationText);
const hasImplementationDirective = /\b(implement|fix|add|change|modify|refactor|build|create|delete|remove)\b/.test(mission.toLowerCase()) && !/record (?:the )?intentional block/.test(mission.toLowerCase());
const missionLower = mission.toLowerCase()
.replace(/do\s+not\s+(?:edit|modify|change)\s+(?:product\s+)?source/g, "")
.replace(/should\s+not\s+change\s+(?:product\s+)?source/g, "")
.replace(/must\s+not\s+(?:edit|modify|change)\s+(?:product\s+)?(?:source|code)/g, "");
const hasImplementationDirective = /\b(implement|fix|add|change|modify|refactor|build|create|delete|remove)\b/.test(missionLower);
return hasCoordinationIntent && !hasImplementationDirective;
}
@@ -268,13 +272,13 @@ function promptFileScopeIsBoardOnly(prompt: string): boolean {
const sourcePathPattern = /(?:^|[\s`'"(])(?:packages|src|source|sources|app|apps|lib|libs|components|scripts|docs|\.github|config|test|tests|__tests__)\//m;
const sourceExtensionPattern = /\.(?:ts|tsx|js|jsx|mjs|cjs|swift|kt|java|py|go|rs|rb|php|cs|cpp|c|h|hpp|json|ya?ml|toml|mdx?|css|scss|html|sql|sh)\b/m;
if (sourcePathPattern.test(normalized) || sourceExtensionPattern.test(normalized)) return false;
const allowedBoardOnlyPattern = /\b(task[- ]?board|board task|task document|task documents|task metadata|task logs|fusion task tools|fn_task_|\.fusion\/tasks|attachments?)\b/;
const allowedBoardOnlyPattern = /(?:^|[^\w/])(?:task[- ]?board|board task|task document|task documents|task metadata|task logs|fusion task tools|fn_task_[\w-]*|\.fusion\/tasks|attachments?)(?=$|[^\w/-])/;
return allowedBoardOnlyPattern.test(normalized);
}
function getNoCommitEligibilityReason(task: Task): "explicit noCommitsExpected=true" | "prompt-derived coordination-only no-source scope" | null {
if (task.noCommitsExpected === true) return "explicit noCommitsExpected=true";
const rawPrompt = (task as { prompt?: unknown }).prompt;
const rawPrompt = task.prompt;
const prompt = typeof rawPrompt === "string" ? rawPrompt : "";
if (!prompt.trim()) return null;
if (