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:
@@ -537,6 +537,19 @@ 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.
|
||||
*/
|
||||
@@ -743,6 +756,32 @@ 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 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -258,7 +258,7 @@ export const DEFAULT_GLOBAL_SETTINGS = {
|
||||
export const DEFAULT_PROJECT_SETTINGS = {
|
||||
globalPause: false,
|
||||
globalPauseReason: undefined,
|
||||
defaultWorkflowId: undefined,
|
||||
defaultWorkflowId: "builtin:coding",
|
||||
enabledBuiltinWorkflowIds: undefined,
|
||||
approvedWorkflowCliCommands: undefined,
|
||||
approvedCliAutonomyAdapters: undefined,
|
||||
@@ -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>;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 } = 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
|
||||
// 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