diff --git a/packages/cli/src/project-resolver.ts b/packages/cli/src/project-resolver.ts index 346678e4bc..6fc82e19b4 100644 --- a/packages/cli/src/project-resolver.ts +++ b/packages/cli/src/project-resolver.ts @@ -18,6 +18,7 @@ import { writeProjectIdentity, detectWorkspaceRepos, saveWorkspaceConfig, + suggestTaskPrefix, type RegisteredProject, type TaskStore, } from "@fusion/core"; @@ -537,19 +538,6 @@ export function suggestProjectName(path: string): string { return parts[parts.length - 1] || "unnamed"; } -/** - * FNXC:TaskPrefix 2026-06-24-18:00: - * Derive a task prefix from a project name by taking the first 2-4 uppercase - * letters. Falls back to "FN" for short names. Used as the suggested default - * during project onboarding so each project gets a recognizable prefix. - */ -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"; -} - /** * Resolve absolute path and validate it exists. */ @@ -698,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( @@ -772,7 +761,8 @@ export async function registerProjectInteractive( const rl = createInterface({ input: process.stdin, output: process.stdout }); const prefixInput = await rl.question(`\n Task prefix [${suggestedPrefix}]: `); rl.close(); - const prefix = prefixInput.trim().toUpperCase() || suggestedPrefix; + const rawPrefix = prefixInput.trim().toUpperCase().replace(/[^A-Z]/g, ""); + const prefix = rawPrefix.length >= 2 ? rawPrefix : suggestedPrefix; await store.updateSettings({ taskPrefix: prefix, diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 309ffdf8d1..7a16c53537 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1957,3 +1957,4 @@ export { clearSyncPassphrase, hasSyncPassphraseConfigured, } from "./secrets-sync-passphrase.js"; +export { suggestTaskPrefix } from "./task-prefix.js"; diff --git a/packages/core/src/settings-schema.ts b/packages/core/src/settings-schema.ts index d57d323ccb..a296a22bdd 100644 --- a/packages/core/src/settings-schema.ts +++ b/packages/core/src/settings-schema.ts @@ -258,7 +258,7 @@ export const DEFAULT_GLOBAL_SETTINGS = { export const DEFAULT_PROJECT_SETTINGS = { globalPause: false, globalPauseReason: undefined, - defaultWorkflowId: "builtin:coding", + defaultWorkflowId: undefined, enabledBuiltinWorkflowIds: undefined, approvedWorkflowCliCommands: undefined, approvedCliAutonomyAdapters: undefined, diff --git a/packages/core/src/task-prefix.ts b/packages/core/src/task-prefix.ts new file mode 100644 index 0000000000..047a39d39d --- /dev/null +++ b/packages/core/src/task-prefix.ts @@ -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"; +} diff --git a/packages/dashboard/src/routes/register-project-routes.ts b/packages/dashboard/src/routes/register-project-routes.ts index da084ec62e..bb28f6f709 100644 --- a/packages/dashboard/src/routes/register-project-routes.ts +++ b/packages/dashboard/src/routes/register-project-routes.ts @@ -382,10 +382,10 @@ export const registerProjectRoutes: ApiRouteRegistrar = (ctx) => { */ if (activeProjectWithOutcome.outcome === "registered") { try { - const { TaskStore } = await import("@fusion/core"); + const { TaskStore, suggestTaskPrefix } = await import("@fusion/core"); const store = new TaskStore(normalizedPath); await store.init(); - const prefix = normalizedName.replace(/[^a-zA-Z]/g, "").toUpperCase().slice(0, 4) || "FN"; + const prefix = suggestTaskPrefix(normalizedName); await store.updateSettings({ taskPrefix: prefix, defaultWorkflowId: "builtin:coding",