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";
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user