refactor: remove legacy kb compatibility

Drops the .kb/kb.db migration path, legacy backup filename handling, and
backward-compat test suites. Renames internal kbDir identifiers to
fusionDir and hasKbProject/isValidKbProject to their fusion equivalents.

- Remove needsCentralMigration, autoMigrateToCentral, and the
  "needs-migration" FirstRunState; checkAndMigrate and KB_SKIP_MIGRATION
  env var are gone
- Remove LEGACY_BACKUP_DIR and canonicalizeBackupDir; listBackups no
  longer matches kb-* filenames
- Delete backward-compat.test.ts and store-backward-compat.test.ts;
  update remaining tests to new 3-state first-run model

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-04-23 16:55:46 -07:00
parent f4e0850f9d
commit f20d0b5a18
36 changed files with 365 additions and 1194 deletions

View File

@@ -189,7 +189,6 @@ vi.mock("./commands/message.js", () => ({
const originalArgv = process.argv;
const originalExit = process.exit;
const originalSkipMigration = process.env.KB_SKIP_MIGRATION;
const originalPiPackageDir = process.env.PI_PACKAGE_DIR;
let importCounter = 0;
@@ -206,7 +205,6 @@ describe("bin command routing and fallbacks", () => {
beforeEach(() => {
vi.clearAllMocks();
process.env.KB_SKIP_MIGRATION = "1";
delete process.env.PI_PACKAGE_DIR;
process.exit = vi.fn(((code?: number) => {
throw new Error(`process.exit:${code ?? 0}`);
@@ -216,11 +214,6 @@ describe("bin command routing and fallbacks", () => {
afterEach(() => {
process.argv = originalArgv;
process.exit = originalExit;
if (originalSkipMigration === undefined) {
delete process.env.KB_SKIP_MIGRATION;
} else {
process.env.KB_SKIP_MIGRATION = originalSkipMigration;
}
if (originalPiPackageDir === undefined) {
delete process.env.PI_PACKAGE_DIR;

View File

@@ -380,51 +380,6 @@ function getFlagValueNumber(args: string[], flag: string): number | undefined {
return Number.isFinite(parsed) ? parsed : undefined;
}
/**
* Check if migration is needed and run it automatically.
* This handles the transition from single-project to multi-project mode.
*/
async function checkAndMigrate(): Promise<void> {
// Skip if KB_SKIP_MIGRATION is set
if (process.env.KB_SKIP_MIGRATION === "1") {
return;
}
try {
const { needsCentralMigration, autoMigrateToCentral } = await import("@fusion/core");
// Check if migration is needed
if (!needsCentralMigration(process.cwd())) {
return;
}
console.log("\n🔄 Migrating to multi-project mode...");
// Get CentralCore and run migration
const { CentralCore } = await import("@fusion/core");
const central = new CentralCore();
await central.init();
try {
const result = await autoMigrateToCentral(process.cwd(), central);
if (result.success) {
console.log(`✓ Registered project: ${result.projectsRegistered.join(", ")}`);
if (result.errors.length > 0) {
console.log(` Warnings: ${result.errors.join(", ")}`);
}
} else {
console.log(`⚠ Migration warnings: ${result.errors.join(", ")}`);
}
} finally {
await central.close();
}
} catch (err) {
// Migration errors are non-fatal - continue with legacy mode
console.log(`⚠ Migration check failed: ${(err as Error).message}`);
}
}
async function main() {
const { cleanedArgs: args, projectName } = extractGlobalProjectFlag(process.argv.slice(2));
@@ -435,12 +390,6 @@ async function main() {
const command = args[0];
// Migration check: run auto-migration for existing single-project users
// Skip for init command itself (user is explicitly initializing)
if (command !== "init" && command !== "dashboard") {
await checkAndMigrate();
}
const {
runDashboard,
runServe,

View File

@@ -21,7 +21,7 @@ vi.mock("@fusion/core", () => ({
TaskStore: vi.fn().mockImplementation(() => ({
init: vi.fn().mockResolvedValue(undefined),
getSettings: mockGetSettings,
kbDir: "/cwd/.fusion",
fusionDir: "/cwd/.fusion",
})),
createBackupManager: vi.fn(() => ({
listBackups: mockListBackups,
@@ -60,7 +60,7 @@ describe("backup commands", () => {
projectName: "demo-project",
projectPath: "/projects/demo",
isRegistered: true,
store: { getSettings: mockGetSettings, kbDir: "/projects/demo/.fusion" },
store: { getSettings: mockGetSettings, fusionDir: "/projects/demo/.fusion" },
});
});

View File

@@ -22,14 +22,14 @@ async function resolveBackupStore(projectName?: string): Promise<TaskStore> {
async function getBackupManager(projectName?: string): Promise<{
manager: BackupManager;
store: TaskStore;
kbDir: string;
fusionDir: string;
}> {
const store = await resolveBackupStore(projectName);
// Access the private kbDir property via type assertion
const kbDir = (store as unknown as { kbDir: string }).kbDir;
// Access the private fusionDir property via type assertion
const fusionDir = (store as unknown as { fusionDir: string }).fusionDir;
const settings = await store.getSettings();
const manager = createBackupManager(kbDir, settings);
return { manager, store, kbDir };
const manager = createBackupManager(fusionDir, settings);
return { manager, store, fusionDir };
}
/**
@@ -37,12 +37,12 @@ async function getBackupManager(projectName?: string): Promise<{
* Usage: fn backup --create
*/
export async function runBackupCreate(projectName?: string): Promise<void> {
const { kbDir, store } = await getBackupManager(projectName);
const { fusionDir, store } = await getBackupManager(projectName);
const settings = await store.getSettings();
console.log("Creating database backup...");
const result = await runBackupCommand(kbDir, settings);
const result = await runBackupCommand(fusionDir, settings);
if (result.success) {
console.log(result.output);

View File

@@ -26,8 +26,8 @@ async function getProjectPath(projectName?: string): Promise<string> {
*/
async function createMessageStore(projectName?: string): Promise<{ store: MessageStore; db: Database }> {
const projectPath = await getProjectPath(projectName);
const kbDir = projectPath + "/.fusion";
const db = createDatabase(kbDir);
const fusionDir = projectPath + "/.fusion";
const db = createDatabase(fusionDir);
db.init();
const store = new MessageStore(db);
return { store, db };

View File

@@ -220,12 +220,12 @@ export async function resolveProject(options: ResolveOptions = {}): Promise<Reso
// 2. Walk up from cwd to find .fusion/
const cwd = options.cwd ? resolve(options.cwd) : process.cwd();
const kbDir = findKbDir(cwd);
const fusionDir = findKbDir(cwd);
if (kbDir) {
if (fusionDir) {
// 3. Match path against registered projects
const allProjects = await central.listProjects();
const normalizedKbDir = normalize(kbDir);
const normalizedKbDir = normalize(fusionDir);
const match = allProjects.find((p) => normalize(p.path) === normalizedKbDir);
@@ -245,12 +245,12 @@ export async function resolveProject(options: ResolveOptions = {}): Promise<Reso
// 4. Has .fusion/ but not registered
if (interactive) {
console.log(`\n Found fn project at ${kbDir} but it's not registered.`);
console.log(`\n Found fn project at ${fusionDir} but it's not registered.`);
const shouldRegister = await promptConfirm("Register this project now?", true);
if (shouldRegister) {
const rl = createInterface({ input: process.stdin, output: process.stdout });
const defaultName = kbDir.split("/").pop() || "unnamed";
const defaultName = fusionDir.split("/").pop() || "unnamed";
const name = await rl.question(` Project name [${defaultName}]: `);
rl.close();
@@ -259,7 +259,7 @@ export async function resolveProject(options: ResolveOptions = {}): Promise<Reso
try {
const newProject = await central.registerProject({
name: finalName,
path: kbDir,
path: fusionDir,
isolationMode: "in-process",
});
@@ -272,22 +272,22 @@ export async function resolveProject(options: ResolveOptions = {}): Promise<Reso
throw new ProjectResolutionError(
`Failed to register project: ${err.message}`,
"NOT_REGISTERED",
{ directory: kbDir, error: err.message }
{ directory: fusionDir, error: err.message }
);
}
} else {
throw new ProjectResolutionError(
"Project not registered. Run `fn project add <path>` to register.",
"NOT_REGISTERED",
{ directory: kbDir }
{ directory: fusionDir }
);
}
} else {
throw new ProjectResolutionError(
`Found fn project at ${kbDir} but it's not registered.\n\n` +
"Run `fn project add " + kbDir + "` to register it, or use --project <name>.",
`Found fn project at ${fusionDir} but it's not registered.\n\n` +
"Run `fn project add " + fusionDir + "` to register it, or use --project <name>.",
"NOT_REGISTERED",
{ directory: kbDir }
{ directory: fusionDir }
);
}
}