feat(workspace): workspace mode toggle, derived task prefix, default coding workflow (#1741)
## Problem After PR #1739 merged the core workspace auto-detection fix, additional workspace UX was needed: 1. Onboarding should interactively confirm workspace mode and let the user pick a task prefix and default workflow. 2. Task prefix should be derived from the project name (2-4 chars uppercase) instead of a hardcoded constant. 3. Default fallback prefix should be `FN` (matching the product name), not the legacy `KB`. 4. Dashboard registration should also auto-derive prefix and set the coding workflow. 5. Default workflow should be `builtin:coding`. ## Changes ### `packages/cli/src/project-resolver.ts` - **Interactive workspace confirmation**: When sub-repos are detected, ask the user to confirm workspace mode instead of auto-applying (non-interactive/dashboard still auto-applies). - **`suggestTaskPrefix(projectName)`**: Derives a 2-4 char prefix from the project name (first letters of words, or first chars of a single word). - **Onboarding prefix prompt**: Shows the suggested prefix and lets the user confirm or override. - **Default coding workflow**: Sets `defaultWorkflowId: "builtin:coding"` for new projects. ### `packages/core/src/settings-schema.ts` - `DEFAULT_PROJECT_SETTINGS.workspaceMode`: changed from `false` to `undefined` so `TaskStore.init()` does not write `workspaceMode: false` to config.json before auto-detection runs (which would block it via `isWorkspaceModeExplicitlyDisabled`). - `DEFAULT_PROJECT_SETTINGS.taskPrefix`: changed to `undefined` (falls back to `"FN"` in store). - `DEFAULT_PROJECT_SETTINGS.defaultWorkflowId`: set to `"builtin:coding"`. ### `packages/core/src/store.ts` - Task prefix fallback changed from `"KB"` to `"FN"`. ### `packages/dashboard/src/routes/register-project-routes.ts` - Auto-derives task prefix from project name for dashboard registrations. - Sets `defaultWorkflowId: "builtin:coding"` for new projects. ## Testing - `pnpm typecheck` — pass - `pnpm lint` — pass - `vitest run git-repository.test.ts` — 8/8 pass <!-- stage-review-badge-begin --> --- <a href="https://stagereview.app/Runfusion/Fusion/pull/1741"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://stagereview.app/assets/gh-open-in-stage-dark.svg"> <img src="https://stagereview.app/assets/gh-open-in-stage-light.svg" alt="Open in Stage"> </picture> </a> <!-- stage-review-badge-end --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Project onboarding now proposes a task prefix derived from the project name, and stores it during setup (interactive onboarding and new registrations). * New projects start with the default coding workflow enabled to improve first-time experience. * **Bug Fixes** * Improved distributed task ID prefix fallback when no custom prefix is configured (now uses `FN`). * **Chores** * Updated default project settings so `taskPrefix` and `workspaceMode` begin unset, aligning with onboarding behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -18,6 +18,7 @@ import {
|
||||
writeProjectIdentity,
|
||||
detectWorkspaceRepos,
|
||||
saveWorkspaceConfig,
|
||||
suggestTaskPrefix,
|
||||
type RegisteredProject,
|
||||
type TaskStore,
|
||||
} from "@fusion/core";
|
||||
@@ -685,6 +686,7 @@ export async function registerProjectInteractive(
|
||||
// Persist workspaceMode in config.json so it's visible/toggleable in the dashboard
|
||||
await store.updateSettings({ workspaceMode: true });
|
||||
}
|
||||
await store.close();
|
||||
console.log(` ✓ Initialized fn at ${absPath}`);
|
||||
} else {
|
||||
throw new ProjectResolutionError(
|
||||
@@ -743,6 +745,33 @@ export async function registerProjectInteractive(
|
||||
// Best-effort stamp only.
|
||||
}
|
||||
|
||||
/*
|
||||
FNXC:Onboarding 2026-06-24-18:00:
|
||||
After registration, prompt the user to confirm a task prefix and default workflow.
|
||||
The prefix defaults to the first 2-4 chars of the project name so each project gets
|
||||
recognizable task IDs (e.g., "MYPR" for "my-project"). The workflow defaults to coding.
|
||||
Both are persisted to config.json via the TaskStore.
|
||||
*/
|
||||
if (interactive) {
|
||||
const { TaskStore } = await import("@fusion/core");
|
||||
const store = new TaskStore(absPath);
|
||||
await store.init();
|
||||
|
||||
const suggestedPrefix = suggestTaskPrefix(name);
|
||||
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
||||
const prefixInput = await rl.question(`\n Task prefix [${suggestedPrefix}]: `);
|
||||
rl.close();
|
||||
const rawPrefix = prefixInput.trim().toUpperCase().replace(/[^A-Z]/g, "");
|
||||
const prefix = rawPrefix.length >= 2 && rawPrefix.length <= 5 ? rawPrefix : suggestedPrefix;
|
||||
|
||||
await store.updateSettings({
|
||||
taskPrefix: prefix,
|
||||
defaultWorkflowId: "builtin:coding",
|
||||
});
|
||||
await store.close();
|
||||
console.log(` ✓ Task prefix set to "${prefix}", default workflow: coding`);
|
||||
}
|
||||
|
||||
return createResolvedProject(project);
|
||||
}
|
||||
|
||||
|
||||
@@ -1957,3 +1957,4 @@ export {
|
||||
clearSyncPassphrase,
|
||||
hasSyncPassphraseConfigured,
|
||||
} from "./secrets-sync-passphrase.js";
|
||||
export { suggestTaskPrefix } from "./task-prefix.js";
|
||||
|
||||
@@ -307,7 +307,7 @@ export const DEFAULT_PROJECT_SETTINGS = {
|
||||
onFailure: "fail",
|
||||
},
|
||||
worktreesDir: undefined,
|
||||
taskPrefix: "FN",
|
||||
taskPrefix: undefined,
|
||||
taskAttributionTrailerNames: ["Fusion-Task-Id"],
|
||||
commitMsgHookEnabled: true,
|
||||
includeTaskIdInCommit: true,
|
||||
@@ -538,7 +538,7 @@ export const DEFAULT_PROJECT_SETTINGS = {
|
||||
researchDefaultTimeout: 300000,
|
||||
researchMaxSourcesPerRun: 20,
|
||||
researchMaxSynthesisRounds: 2,
|
||||
workspaceMode: false,
|
||||
workspaceMode: undefined,
|
||||
} satisfies CompleteSettings<ProjectSettingsSchema>;
|
||||
|
||||
/**
|
||||
|
||||
@@ -4151,7 +4151,7 @@ ${TASK_UPSERT_SQL_ASSIGNMENTS}
|
||||
},
|
||||
): Promise<Task> {
|
||||
const settings = await this.getSettingsFast();
|
||||
const prefix = (settings.taskPrefix || "KB").trim().toUpperCase();
|
||||
const prefix = (settings.taskPrefix || "FN").trim().toUpperCase();
|
||||
const allocator = this.getDistributedTaskIdAllocator();
|
||||
const nodeId = await this.resolveLocalNodeIdForTaskAllocation();
|
||||
const reservation = await allocator.reserveDistributedTaskId({
|
||||
|
||||
17
packages/core/src/task-prefix.ts
Normal file
17
packages/core/src/task-prefix.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* FNXC:TaskPrefix 2026-06-24-18:00:
|
||||
* Derive a task prefix from a project name. Strips non-alpha characters, uppercases,
|
||||
* and takes the first 2-4 characters. Falls back to "FN" for names with fewer than
|
||||
* 2 letters. Used during project onboarding (CLI and dashboard) so each project gets
|
||||
* a recognizable prefix for task IDs (e.g. "MYPR" for "my-project").
|
||||
*
|
||||
* Note: the result is the first 2-4 letters of the cleaned (alpha-only, uppercased)
|
||||
* name, NOT the initials of each word. For "my-project" the result is "MYPR"
|
||||
* (first 4 of "MYPROJECT"), not "MP".
|
||||
*/
|
||||
export function suggestTaskPrefix(projectName: string): string {
|
||||
const cleaned = projectName.replace(/[^a-zA-Z]/g, "").toUpperCase();
|
||||
if (cleaned.length >= 2 && cleaned.length <= 4) return cleaned;
|
||||
if (cleaned.length > 4) return cleaned.slice(0, 4);
|
||||
return "FN";
|
||||
}
|
||||
@@ -374,6 +374,28 @@ export const registerProjectRoutes: ApiRouteRegistrar = (ctx) => {
|
||||
// Memory bootstrap failure is non-fatal - project registration succeeded
|
||||
});
|
||||
|
||||
/*
|
||||
FNXC:Onboarding 2026-06-24-18:00:
|
||||
For new registrations (not reattachments), set a derived task prefix and default
|
||||
workflow via the per-project TaskStore config.json so the project is immediately
|
||||
usable without manual settings configuration.
|
||||
*/
|
||||
if (activeProjectWithOutcome.outcome === "registered") {
|
||||
try {
|
||||
const { TaskStore, suggestTaskPrefix } = await import("@fusion/core");
|
||||
const store = new TaskStore(normalizedPath);
|
||||
await store.init();
|
||||
const prefix = suggestTaskPrefix(normalizedName);
|
||||
await store.updateSettings({
|
||||
taskPrefix: prefix,
|
||||
defaultWorkflowId: "builtin:coding",
|
||||
});
|
||||
await store.close();
|
||||
} catch {
|
||||
// Non-fatal: project registration succeeded; settings can be configured later
|
||||
}
|
||||
}
|
||||
|
||||
// Notify the host (serve.ts/daemon.ts) so it can run project-setup
|
||||
// side-effects like installing the fusion Claude-skill into
|
||||
// .claude/skills/fusion when pi-claude-cli is configured. The callback
|
||||
|
||||
Reference in New Issue
Block a user