feat: add per-task GitHub tracking overrides to task create (fn_task_create + CLI)
fn_task_create gains github_tracking/github_repo params and fn task create gains --github/--no-github/--github-repo flags, resolved through resolveTaskGithubTracking (task > project > global). CLI create now also honors the project/global tracking-enabled default it previously ignored; explicit disables persist enabled:false so later default flips cannot re-enable a task. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
7
.changeset/task-create-github-tracking-flags.md
Normal file
7
.changeset/task-create-github-tracking-flags.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": minor
|
||||
---
|
||||
|
||||
summary: Task creation now accepts per-task GitHub tracking overrides (fn_task_create params and `fn task create --github`).
|
||||
category: feature
|
||||
dev: New `github_tracking`/`github_repo` params on fn_task_create and `--github`/`--no-github`/`--github-repo` flags on `fn task create`. CLI create now also applies the project/global "tracking enabled by default" setting it previously ignored; explicit disables persist `githubTracking.enabled:false`.
|
||||
@@ -637,6 +637,7 @@ Task lifecycle and task operations.
|
||||
fn task create "Fix login race condition"
|
||||
fn task create "Fix bug" --attach screenshot.png --depends FN-010
|
||||
fn task create "Investigate flaky runner" --node edge-runner
|
||||
fn task create "Fix workspace revert" --github --github-repo acme/kb
|
||||
fn task plan "Design a new authentication flow"
|
||||
```
|
||||
|
||||
@@ -1383,6 +1384,8 @@ Subcommands: `search`, `install`, `get`.
|
||||
| `--attach` | `fn task create` |
|
||||
| `--depends` | `fn task create` |
|
||||
| `--node` | `fn task create` |
|
||||
| `--github` / `--no-github` | `fn task create` (per-task GitHub issue tracking override; default comes from project/global settings) |
|
||||
| `--github-repo` | `fn task create` (`owner/repo` override for the tracking issue) |
|
||||
| `--feedback` | `fn task refine` |
|
||||
| `--yes` | confirmation-skipping flows (`task plan`, `settings import`, git pull/push, etc.) |
|
||||
| `--limit`, `-l` | `fn task import`, `fn task import-gitlab` (default: 30, max: 100), `fn skills search` (default: 10, max: 50) |
|
||||
|
||||
@@ -19,6 +19,8 @@ Create a new task on the Fusion task board. The task enters the planning column
|
||||
| `agentId` | string | — | Agent ID to assign this task to (e.g. 'agent-abc123') |
|
||||
| `priority` | string(enum) | — | Task priority (low, normal, high, urgent) |
|
||||
| `workflow_id` | string | — | Workflow ID to select for the new task (e.g. 'WF-003' or 'builtin:coding'). Omit to inherit the project default workflow. Use fn_workflow_list to discover valid IDs. |
|
||||
| `github_tracking` | boolean | — | Per-task GitHub issue tracking override. true links a tracking issue to this task; false disables tracking even when the project/global default enables it. Omit to inherit the project/global default. |
|
||||
| `github_repo` | string | — | "owner/repo" override for the GitHub tracking issue's repository. Omit to use the project/global default repo. |
|
||||
|
||||
### fn_task_update
|
||||
|
||||
|
||||
@@ -3422,6 +3422,58 @@ pgTest("fn pi extension (runnable structured-output regression slice)", () => {
|
||||
expect(result.content[0].text).toContain("requires an \"executor\"-role agent");
|
||||
});
|
||||
|
||||
it("fn_task_create persists per-task github tracking overrides from github_tracking/github_repo", async () => {
|
||||
const createTool = api.tools.get("fn_task_create")!;
|
||||
const result = await createTool.execute(
|
||||
"create-gh-on",
|
||||
{ description: "Track me on GitHub", github_tracking: true, github_repo: "acme/widgets" },
|
||||
undefined,
|
||||
undefined,
|
||||
makeCtx(tmpDir),
|
||||
);
|
||||
const task = await h.store().getTask(result.details.taskId);
|
||||
expect(task.githubTracking?.enabled).toBe(true);
|
||||
expect(task.githubTracking?.repoOverride).toBe("acme/widgets");
|
||||
|
||||
const invalid = await createTool.execute(
|
||||
"create-gh-bad-repo",
|
||||
{ description: "Bad repo slug", github_repo: "not a slug" },
|
||||
undefined,
|
||||
undefined,
|
||||
makeCtx(tmpDir),
|
||||
);
|
||||
expect(invalid.isError).toBe(true);
|
||||
expect(invalid.content[0].text).toContain("owner/repo");
|
||||
});
|
||||
|
||||
it("fn_task_create persists an explicit github_tracking:false even when the project default enables tracking", async () => {
|
||||
await h.store().updateSettings({ githubTrackingEnabledByDefault: true });
|
||||
try {
|
||||
const createTool = api.tools.get("fn_task_create")!;
|
||||
const offResult = await createTool.execute(
|
||||
"create-gh-off",
|
||||
{ description: "Opt out of GitHub tracking", github_tracking: false },
|
||||
undefined,
|
||||
undefined,
|
||||
makeCtx(tmpDir),
|
||||
);
|
||||
const offTask = await h.store().getTask(offResult.details.taskId);
|
||||
expect(offTask.githubTracking?.enabled).toBe(false);
|
||||
|
||||
const defaultResult = await createTool.execute(
|
||||
"create-gh-default",
|
||||
{ description: "Inherit project GitHub tracking default" },
|
||||
undefined,
|
||||
undefined,
|
||||
makeCtx(tmpDir),
|
||||
);
|
||||
const defaultTask = await h.store().getTask(defaultResult.details.taskId);
|
||||
expect(defaultTask.githubTracking?.enabled).toBe(true);
|
||||
} finally {
|
||||
await h.store().updateSettings({ githubTrackingEnabledByDefault: false });
|
||||
}
|
||||
});
|
||||
|
||||
it("fn_task_update rejects reviewer assignment for implementation tasks", async () => {
|
||||
const agentStore = new AgentStore({ rootDir: join(tmpDir, ".fusion"), asyncLayer: h.store().getAsyncLayer() });
|
||||
await agentStore.init();
|
||||
|
||||
@@ -518,6 +518,9 @@ Options:
|
||||
--attach <file> Attach file(s) on task create (repeatable)
|
||||
--depends <id> Declare dependency on task create (repeatable)
|
||||
--no-dedup Bypass deterministic duplicate guard on task create
|
||||
--github Enable GitHub issue tracking for the created task
|
||||
--no-github Disable GitHub issue tracking (overrides project default)
|
||||
--github-repo <owner/repo> Repository override for the task's tracking issue
|
||||
--feedback <text> Refinement feedback (non-interactive mode)
|
||||
--yes Skip confirmation prompts (planning mode)
|
||||
--limit, -l <n> Max issues to import (default: 30, max: 100)
|
||||
@@ -1266,6 +1269,8 @@ async function main() {
|
||||
const dependsIds: string[] = [];
|
||||
let nodeName: string | undefined;
|
||||
let noDedup = false;
|
||||
let github: boolean | undefined;
|
||||
let githubRepo: string | undefined;
|
||||
const descParts: string[] = [];
|
||||
for (let i = 0; i < createArgs.length; i++) {
|
||||
if (createArgs[i] === "--attach" && i + 1 < createArgs.length) {
|
||||
@@ -1279,12 +1284,19 @@ async function main() {
|
||||
i++; // skip the value
|
||||
} else if (createArgs[i] === "--no-dedup") {
|
||||
noDedup = true;
|
||||
} else if (createArgs[i] === "--github") {
|
||||
github = true;
|
||||
} else if (createArgs[i] === "--no-github") {
|
||||
github = false;
|
||||
} else if (createArgs[i] === "--github-repo" && i + 1 < createArgs.length) {
|
||||
githubRepo = createArgs[i + 1];
|
||||
i++; // skip the value
|
||||
} else {
|
||||
descParts.push(createArgs[i]);
|
||||
}
|
||||
}
|
||||
const title = descParts.join(" ");
|
||||
await runTaskCreate(title || undefined, attachFiles.length > 0 ? attachFiles : undefined, dependsIds.length > 0 ? dependsIds : undefined, projectName, nodeName, noDedup);
|
||||
await runTaskCreate(title || undefined, attachFiles.length > 0 ? attachFiles : undefined, dependsIds.length > 0 ? dependsIds : undefined, projectName, nodeName, noDedup, github !== undefined || githubRepo !== undefined ? { github, githubRepo } : undefined);
|
||||
break;
|
||||
}
|
||||
case "plan": {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TaskStore, COLUMNS, COLUMN_LABELS, resolveProjectColumnsForRoles, TERMINAL_ROLES, resolveReviewColumns, resolveTaskLifecycleColumns, resolveWorkflowIrForTask, CentralCore, buildAutoPauseClearPatch, buildManualRetryResetPatch, extractIntentSignature, findNearDuplicates, getTaskDuplicateLineage, isWorkspaceTask, reconcileDeterministicDuplicate, resolveTaskGithubTracking, runDeterministicDuplicateGuard, type Settings, type Column, type ColumnId, type StepStatus, type AgentLogType, type AgentLogEntry, type IntentSignature, type NearDuplicateCandidate, type NearDuplicateMatch, type TaskDependencyMutation } from "@fusion/core";
|
||||
import { TaskStore, COLUMNS, COLUMN_LABELS, resolveProjectColumnsForRoles, TERMINAL_ROLES, resolveReviewColumns, resolveTaskLifecycleColumns, resolveWorkflowIrForTask, CentralCore, buildAutoPauseClearPatch, buildManualRetryResetPatch, extractIntentSignature, findNearDuplicates, getTaskDuplicateLineage, isValidRepoSlug, isWorkspaceTask, reconcileDeterministicDuplicate, resolveTaskGithubTracking, runDeterministicDuplicateGuard, type Settings, type Column, type ColumnId, type StepStatus, type AgentLogType, type AgentLogEntry, type IntentSignature, type NearDuplicateCandidate, type NearDuplicateMatch, type TaskDependencyMutation } from "@fusion/core";
|
||||
import { isInReviewMissingWorktreeSessionStartFailure, runAiMerge, landWorkspaceTask, installBaselineArchiveWorktreeDisposer } from "@fusion/engine";
|
||||
import { createInterface } from "node:readline/promises";
|
||||
import type { PlanningQuestion, PlanningSummary } from "@fusion/core";
|
||||
@@ -444,9 +444,23 @@ async function runCliNearDuplicateCheck(args: {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
export async function runTaskCreate(descriptionArg?: string, attachFiles?: string[], depends?: string[], projectName?: string, nodeName?: string, noDedup = false) {
|
||||
export async function runTaskCreate(descriptionArg?: string, attachFiles?: string[], depends?: string[], projectName?: string, nodeName?: string, noDedup = false, githubOpts?: { github?: boolean; githubRepo?: string }) {
|
||||
let description = descriptionArg;
|
||||
|
||||
/*
|
||||
FNXC:GithubTracking 2026-08-15-03:50:
|
||||
`fn task create --github [--github-repo owner/repo]` decides tracking at CREATE time,
|
||||
because the tracking lifecycle keys off the persisted `task.githubTracking.enabled === true`
|
||||
flag and never re-resolves project/global defaults for existing tasks. Before this flag,
|
||||
CLI-created tasks could not opt in at all (only the dashboard and fn_task_create could).
|
||||
Explicit `--no-github` persists `enabled:false` so a later default flip cannot re-enable it.
|
||||
*/
|
||||
const githubRepoOverride = githubOpts?.githubRepo?.trim() || undefined;
|
||||
if (githubRepoOverride && !isValidRepoSlug(githubRepoOverride)) {
|
||||
console.error(`Invalid --github-repo "${githubRepoOverride}" — expected "owner/repo".`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!description) {
|
||||
const rl = createInterface({ input: process.stdin, output: promptOutputStream() });
|
||||
description = await rl.question("Task description: ");
|
||||
@@ -511,10 +525,42 @@ export async function runTaskCreate(descriptionArg?: string, attachFiles?: strin
|
||||
id, so a stale value can never make CLI create throw.
|
||||
*/
|
||||
const originWorkflowId = await store.resolveOriginWorkflowOverrideId("task-create");
|
||||
/*
|
||||
FNXC:GithubTracking 2026-08-15-03:50:
|
||||
Same create-time resolution as fn_task_create: CLI flag > project default >
|
||||
global default. Also fixes CLI creates silently ignoring a project's
|
||||
"GitHub tracking enabled by default" setting (the persisted flag is the only
|
||||
thing the tracking lifecycle reads).
|
||||
*/
|
||||
const globalSettingsForTracking = await store.getGlobalSettingsStore().getSettings();
|
||||
const resolvedTracking = resolveTaskGithubTracking(
|
||||
{
|
||||
githubTracking:
|
||||
githubOpts?.github !== undefined || githubRepoOverride
|
||||
? {
|
||||
...(githubOpts?.github !== undefined ? { enabled: githubOpts.github } : {}),
|
||||
...(githubRepoOverride ? { repoOverride: githubRepoOverride } : {}),
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
await store.getSettings(),
|
||||
globalSettingsForTracking,
|
||||
);
|
||||
const githubTracking = resolvedTracking.enabled
|
||||
? {
|
||||
enabled: true as const,
|
||||
...(resolvedTracking.repo
|
||||
? { repoOverride: `${resolvedTracking.repo.owner}/${resolvedTracking.repo.repo}` }
|
||||
: {}),
|
||||
}
|
||||
: githubOpts?.github === false
|
||||
? { enabled: false as const }
|
||||
: undefined;
|
||||
const created = await store.createTask({
|
||||
description: trimmedDescription,
|
||||
dependencies: depends,
|
||||
...(originWorkflowId ? { workflowId: originWorkflowId } : {}),
|
||||
...(githubTracking ? { githubTracking } : {}),
|
||||
source: {
|
||||
sourceType: "cli",
|
||||
sourceMetadata: Object.keys(sourceMetadata).length > 0 ? sourceMetadata : undefined,
|
||||
|
||||
@@ -1664,6 +1664,21 @@ export default function kbExtension(pi: ExtensionAPI) {
|
||||
"Omit to inherit the project default workflow. Use fn_workflow_list to discover valid IDs.",
|
||||
}),
|
||||
),
|
||||
github_tracking: Type.Optional(
|
||||
Type.Boolean({
|
||||
description:
|
||||
"Per-task GitHub issue tracking override. true links a tracking issue to this task; " +
|
||||
"false disables tracking even when the project/global default enables it. " +
|
||||
"Omit to inherit the project/global default.",
|
||||
}),
|
||||
),
|
||||
github_repo: Type.Optional(
|
||||
Type.String({
|
||||
description:
|
||||
"\"owner/repo\" override for the GitHub tracking issue's repository. " +
|
||||
"Omit to use the project/global default repo.",
|
||||
}),
|
||||
),
|
||||
}),
|
||||
|
||||
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
||||
@@ -1727,10 +1742,33 @@ export default function kbExtension(pi: ExtensionAPI) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
FNXC:GithubTracking 2026-08-15-03:50:
|
||||
Per-task GitHub tracking is decided at CREATE time (the lifecycle hooks key off the
|
||||
persisted `task.githubTracking.enabled === true` flag, never re-resolving defaults),
|
||||
so callers need a create-time override. `github_tracking`/`github_repo` feed the
|
||||
task-level slot of resolveTaskGithubTracking, which already gives task > project >
|
||||
global precedence. An explicit `false` is persisted (not dropped) so a later flip of
|
||||
the project default cannot retroactively enable tracking for this task.
|
||||
*/
|
||||
const githubRepoOverride = params.github_repo?.trim() || undefined;
|
||||
if (githubRepoOverride && !fusionCore.isValidRepoSlug(githubRepoOverride)) {
|
||||
const error = `Invalid github_repo "${githubRepoOverride}" — expected "owner/repo".`;
|
||||
return { content: [{ type: "text", text: `ERROR: ${error}` }], isError: true, details: { error } };
|
||||
}
|
||||
|
||||
try {
|
||||
const globalSettings = await store.getGlobalSettingsStore().getSettings();
|
||||
const resolvedTracking = resolveTaskGithubTracking(
|
||||
{ githubTracking: undefined },
|
||||
{
|
||||
githubTracking:
|
||||
params.github_tracking !== undefined || githubRepoOverride
|
||||
? {
|
||||
...(params.github_tracking !== undefined ? { enabled: params.github_tracking } : {}),
|
||||
...(githubRepoOverride ? { repoOverride: githubRepoOverride } : {}),
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
projectSettingsForGate,
|
||||
globalSettings,
|
||||
);
|
||||
@@ -1760,7 +1798,9 @@ export default function kbExtension(pi: ExtensionAPI) {
|
||||
? { repoOverride: `${resolvedTracking.repo.owner}/${resolvedTracking.repo.repo}` }
|
||||
: {}),
|
||||
}
|
||||
: undefined,
|
||||
: params.github_tracking === false
|
||||
? { enabled: false }
|
||||
: undefined,
|
||||
}, { rootDir: ctx.cwd, sourceAgentId: fnCtx.agentId, sourceTaskId: fnCtx.taskId });
|
||||
|
||||
const label =
|
||||
|
||||
Reference in New Issue
Block a user