feat(workspace): fix auto-detection, derive prefix from name, default coding workflow

- Fix workspace detection: change workspaceMode default from false to
  undefined so isWorkspaceModeExplicitlyDisabled no longer blocks
  auto-detection on fresh projects (config.json was being written with
  workspaceMode:false during store.init(), causing the guard to skip
  detection before it ever ran)
- Derive task prefix from project name (first 2-4 chars) instead of
  hardcoded 'FN' as the suggested default
- Default workflow is now builtin:coding instead of undefined
- CLI registerProjectInteractive: onboarding prompt for task prefix
  confirmation after project name
- Dashboard POST /api/projects: auto-derive prefix and set default
  workflow for new registrations
This commit is contained in:
gsxdsm
2026-06-24 10:20:00 -07:00
parent e1f419ae17
commit 800f845e15
3 changed files with 64 additions and 3 deletions

View File

@@ -537,6 +537,19 @@ export function suggestProjectName(path: string): string {
return parts[parts.length - 1] || "unnamed"; 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. * Resolve absolute path and validate it exists.
*/ */
@@ -743,6 +756,32 @@ export async function registerProjectInteractive(
// Best-effort stamp only. // 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 prefix = prefixInput.trim().toUpperCase() || 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); return createResolvedProject(project);
} }

View File

@@ -258,7 +258,7 @@ export const DEFAULT_GLOBAL_SETTINGS = {
export const DEFAULT_PROJECT_SETTINGS = { export const DEFAULT_PROJECT_SETTINGS = {
globalPause: false, globalPause: false,
globalPauseReason: undefined, globalPauseReason: undefined,
defaultWorkflowId: undefined, defaultWorkflowId: "builtin:coding",
enabledBuiltinWorkflowIds: undefined, enabledBuiltinWorkflowIds: undefined,
approvedWorkflowCliCommands: undefined, approvedWorkflowCliCommands: undefined,
approvedCliAutonomyAdapters: undefined, approvedCliAutonomyAdapters: undefined,
@@ -307,7 +307,7 @@ export const DEFAULT_PROJECT_SETTINGS = {
onFailure: "fail", onFailure: "fail",
}, },
worktreesDir: undefined, worktreesDir: undefined,
taskPrefix: "FN", taskPrefix: undefined,
taskAttributionTrailerNames: ["Fusion-Task-Id"], taskAttributionTrailerNames: ["Fusion-Task-Id"],
commitMsgHookEnabled: true, commitMsgHookEnabled: true,
includeTaskIdInCommit: true, includeTaskIdInCommit: true,
@@ -538,7 +538,7 @@ export const DEFAULT_PROJECT_SETTINGS = {
researchDefaultTimeout: 300000, researchDefaultTimeout: 300000,
researchMaxSourcesPerRun: 20, researchMaxSourcesPerRun: 20,
researchMaxSynthesisRounds: 2, researchMaxSynthesisRounds: 2,
workspaceMode: false, workspaceMode: undefined,
} satisfies CompleteSettings<ProjectSettingsSchema>; } satisfies CompleteSettings<ProjectSettingsSchema>;
/** /**

View File

@@ -374,6 +374,28 @@ export const registerProjectRoutes: ApiRouteRegistrar = (ctx) => {
// Memory bootstrap failure is non-fatal - project registration succeeded // 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 } = 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";
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 // Notify the host (serve.ts/daemon.ts) so it can run project-setup
// side-effects like installing the fusion Claude-skill into // side-effects like installing the fusion Claude-skill into
// .claude/skills/fusion when pi-claude-cli is configured. The callback // .claude/skills/fusion when pi-claude-cli is configured. The callback