feat(FN-1163): align command references with fn naming

- Update CLI command strings, help text, and project-resolution messaging to reference fn
- Sync core backup/store messaging and dashboard API/planning/subtask prompt examples with fn usage
- Refresh dashboard UI copy in settings, project detection, and PR guidance for consistent command naming
- Adjust CLI/dashboard tests and add a @gsxdsm/fusion changeset for the naming alignment patch
This commit is contained in:
gsxdsm
2026-04-08 11:00:32 -07:00
parent 6ac147b8fe
commit 4f48d2d186
30 changed files with 77 additions and 72 deletions

View File

@@ -161,7 +161,7 @@ describe("build-exe", () => {
child!.stdout!.on("data", (d: Buffer) => {
startupOutput += d.toString();
if (
startupOutput.includes("kb board") &&
startupOutput.includes("fn board") &&
startupOutput.includes(`→ http://localhost:${port}`)
) {
settle("ready");

View File

@@ -57,7 +57,7 @@ function makeCtx(cwd: string) {
// ── Tests ──────────────────────────────────────────────────────────
describe("kb pi extension", () => {
describe("fn pi extension", () => {
let tmpDir: string;
let api: ReturnType<typeof createMockAPI>;

View File

@@ -59,7 +59,7 @@ const HELP = `
fn — AI-orchestrated task board
Usage:
fn init [opts] Initialize a new kb project in the current directory
fn init [opts] Initialize a new fn project in the current directory
fn dashboard Start the board web UI
fn dashboard --paused Start with automation paused
fn dashboard --dev Start web UI only (no AI engine)

View File

@@ -104,7 +104,7 @@ describe("backup commands", () => {
it("runBackupList without project falls back to current cwd task store when resolution fails", async () => {
const cwdSpy = vi.spyOn(process, "cwd").mockReturnValue("/local/project");
mockResolveProject.mockRejectedValueOnce(new Error("No kb project found"));
mockResolveProject.mockRejectedValueOnce(new Error("No fn project found"));
await runBackupList();
expect(mockResolveProject).toHaveBeenCalledWith(undefined);
expect(TaskStore).toHaveBeenCalledWith("/local/project");
@@ -113,7 +113,7 @@ describe("backup commands", () => {
it("falls back to current cwd task store when project resolution fails for project-targeted commands", async () => {
const cwdSpy = vi.spyOn(process, "cwd").mockReturnValue("/fallback/project");
mockResolveProject.mockRejectedValue(new Error("Project 'missing' not found. Run 'kb project list' to see registered projects."));
mockResolveProject.mockRejectedValue(new Error("Project 'missing' not found. Run 'fn project list' to see registered projects."));
await runBackupList("missing");
expect(TaskStore).toHaveBeenCalledWith("/fallback/project");

View File

@@ -34,7 +34,7 @@ async function getBackupManager(projectName?: string): Promise<{
/**
* Create a database backup immediately.
* Usage: kb backup --create
* Usage: fn backup --create
*/
export async function runBackupCreate(projectName?: string): Promise<void> {
const { manager, kbDir, store } = await getBackupManager(projectName);
@@ -55,7 +55,7 @@ export async function runBackupCreate(projectName?: string): Promise<void> {
/**
* List all database backups.
* Usage: kb backup --list
* Usage: fn backup --list
*/
export async function runBackupList(projectName?: string): Promise<void> {
const { manager } = await getBackupManager(projectName);
@@ -88,7 +88,7 @@ export async function runBackupList(projectName?: string): Promise<void> {
/**
* Restore database from a backup file.
* Usage: kb backup --restore <filename>
* Usage: fn backup --restore <filename>
*/
export async function runBackupRestore(filename: string, projectName?: string): Promise<void> {
const { manager } = await getBackupManager(projectName);
@@ -108,7 +108,7 @@ export async function runBackupRestore(filename: string, projectName?: string):
/**
* Remove old backups exceeding retention limit.
* Usage: kb backup --cleanup
* Usage: fn backup --cleanup
*/
export async function runBackupCleanup(projectName?: string): Promise<void> {
const { manager } = await getBackupManager(projectName);

View File

@@ -840,7 +840,7 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
}
console.log();
console.log(` kb board`);
console.log(` fn board`);
console.log(` ────────────────────────`);
console.log(` → http://localhost:${actualPort}`);
console.log();

View File

@@ -126,7 +126,7 @@ describe("git commands", () => {
});
it("propagates project resolution errors for git commands", async () => {
vi.mocked(resolveProject).mockRejectedValue(new Error("Project 'missing' not found. Run 'kb project list' to see registered projects."));
vi.mocked(resolveProject).mockRejectedValue(new Error("Project 'missing' not found. Run 'fn project list' to see registered projects."));
await expect(runGitFetch("origin", "missing")).rejects.toThrow("Project 'missing' not found");
});

View File

@@ -1,7 +1,7 @@
/**
* Init command for kb CLI.
* Init command for fn CLI.
*
* Initializes a new kb project in the current directory by:
* Initializes a new fn project in the current directory by:
* 1. Creating the .fusion/ directory with fusion.db
* 2. Registering the project in the central database
*
@@ -42,7 +42,7 @@ export async function runInit(options: InitOptions = {}): Promise<void> {
const existing = await central.getProjectByPath(cwd);
if (existing) {
console.log(`kb project already initialized: "${existing.name}"`);
console.log(`fn project already initialized: "${existing.name}"`);
console.log(` Path: ${cwd}`);
console.log(`\n Project is registered in the central registry.`);
console.log(` To re-initialize with a different name, run:`);
@@ -64,7 +64,7 @@ export async function runInit(options: InitOptions = {}): Promise<void> {
// Get or generate project name
const projectName = options.name ?? detectProjectName(cwd);
console.log(`Initializing kb project: "${projectName}"`);
console.log(`Initializing fn project: "${projectName}"`);
console.log(` Path: ${cwd}`);
// Create .fusion/ directory

View File

@@ -38,7 +38,7 @@ export async function runNodeList(options: NodeListOptions = {}): Promise<void>
console.log(JSON.stringify([], null, 2));
} else {
console.log("\n No nodes registered.");
console.log(" Register one with: kb node add <name>\n");
console.log(" Register one with: fn node add <name>\n");
}
return;
}
@@ -78,7 +78,7 @@ export async function runNodeList(options: NodeListOptions = {}): Promise<void>
*/
export async function runNodeAdd(name: string, options: NodeAddOptions = {}): Promise<void> {
if (!name) {
console.error("Usage: kb node add <name> [--url <url>] [--api-key <key>] [--max-concurrent <n>]");
console.error("Usage: fn node add <name> [--url <url>] [--api-key <key>] [--max-concurrent <n>]");
process.exit(1);
}
@@ -140,7 +140,7 @@ export async function runNodeAdd(name: string, options: NodeAddOptions = {}): Pr
*/
export async function runNodeRemove(name: string, options: NodeRemoveOptions = {}): Promise<void> {
if (!name) {
console.error("Usage: kb node remove <name> [--force]");
console.error("Usage: fn node remove <name> [--force]");
process.exit(1);
}
@@ -223,7 +223,7 @@ export async function runNodeShow(name?: string): Promise<void> {
*/
export async function runNodeHealth(name: string): Promise<void> {
if (!name) {
console.error("Usage: kb node health <name>");
console.error("Usage: fn node health <name>");
process.exit(1);
}

View File

@@ -1,5 +1,5 @@
/**
* Project command implementations for kb CLI.
* Project command implementations for fn CLI.
*
* Provides commands for managing the project registry:
* - list: List all registered projects
@@ -157,7 +157,7 @@ export async function runProjectList(options: ProjectListOptions = {}): Promise<
console.log(JSON.stringify([], null, 2));
} else {
console.log("\n No projects registered.");
console.log(" Register one with: kb project add <name> <path>\n");
console.log(" Register one with: fn project add <name> <path>\n");
}
return;
}
@@ -281,17 +281,17 @@ export async function runProjectAdd(
// Check for .fusion directory
const kbDbPath = resolve(absolutePath, ".fusion", "fusion.db");
if (!existsSync(kbDbPath) && !options.force) {
console.log(`\n No kb project found at ${formatDisplayPath(absolutePath)}`);
const init = await rl.question(" Initialize kb here first? [Y/n] ");
console.log(`\n No fn project found at ${formatDisplayPath(absolutePath)}`);
const init = await rl.question(" Initialize fn here first? [Y/n] ");
rl.close();
if (init.trim().toLowerCase() !== "n") {
// Initialize the project
const store = new TaskStore(absolutePath);
await store.init();
console.log(` ✓ Initialized kb at ${absolutePath}`);
console.log(` ✓ Initialized fn at ${absolutePath}`);
} else {
console.log("\n Cancelled. Run `kb init` to initialize a project first.\n");
console.log("\n Cancelled. Run `fn init` to initialize a project first.\n");
process.exit(1);
}
}
@@ -334,8 +334,8 @@ export async function runProjectAdd(
// Check for .fusion directory
const kbDbPath = resolve(absolutePath, ".fusion", "fusion.db");
if (!existsSync(kbDbPath) && !options.force) {
console.error(`\n ✗ No kb project found at ${formatDisplayPath(absolutePath)}`);
console.error(" Run `kb init` first to initialize the project.\n");
console.error(`\n ✗ No fn project found at ${formatDisplayPath(absolutePath)}`);
console.error(" Run `fn init` first to initialize the project.\n");
process.exit(1);
}
@@ -383,7 +383,7 @@ export async function runProjectAdd(
*/
export async function runProjectRemove(name: string, options: ProjectRemoveOptions = {}): Promise<void> {
if (!name) {
console.error("Usage: kb project remove <name> [--force]");
console.error("Usage: fn project remove <name> [--force]");
process.exit(1);
}
@@ -414,7 +414,7 @@ export async function runProjectRemove(name: string, options: ProjectRemoveOptio
console.log(` Location: ${formatDisplayPath(project.path)}`);
console.log();
console.log(" Note: Project data is preserved. You can re-register with:");
console.log(` kb project add ${project.name} ${project.path}`);
console.log(` fn project add ${project.name} ${project.path}`);
console.log();
} finally {
await central.close();
@@ -461,7 +461,7 @@ export async function runProjectShow(name?: string): Promise<void> {
}
}
console.log();
console.log(" Run 'kb project add' to register this project.");
console.log(" Run 'fn project add' to register this project.");
console.log();
return;
}
@@ -530,7 +530,7 @@ export const runProjectInfo = runProjectShow;
*/
export async function runProjectSetDefault(name: string): Promise<void> {
if (!name) {
console.error("Usage: kb project set-default <name>");
console.error("Usage: fn project set-default <name>");
process.exit(1);
}
@@ -579,7 +579,7 @@ export async function runProjectDetect(): Promise<void> {
console.log();
} else {
console.log();
console.log(" No kb project detected from current directory.");
console.log(" No fn project detected from current directory.");
console.log();
}
} finally {

View File

@@ -5,7 +5,7 @@ import { resolveProject } from "../project-context.js";
/**
* Run settings export command.
* Usage: kb settings export [--output <path>] [--scope global|project|both]
* Usage: fn settings export [--output <path>] [--scope global|project|both]
*
* @param options.output - Custom output file path (optional, auto-generates if not provided)
* @param options.scope - Which settings to export: 'global', 'project', or 'both' (default: 'both')

View File

@@ -5,7 +5,7 @@ import { resolveProject } from "../project-context.js";
/**
* Run settings import command.
* Usage: kb settings import <file> [--scope global|project|both] [--merge] [--yes]
* Usage: fn settings import <file> [--scope global|project|both] [--merge] [--yes]
*
* @param filePath - Path to the JSON file to import
* @param options.scope - Which settings to import: 'global', 'project', or 'both' (default: 'both')

View File

@@ -78,7 +78,7 @@ describe("settings commands", () => {
expect(getSettings).toHaveBeenCalled();
expect(resolveProject).not.toHaveBeenCalled();
expect(logSpy).toHaveBeenCalledWith(" kb Global Settings");
expect(logSpy).toHaveBeenCalledWith(" fn Global Settings");
});
it("runSettingsShow with project uses project store", async () => {
@@ -94,7 +94,7 @@ describe("settings commands", () => {
await runSettingsShow("demo-project");
expect(resolveProject).toHaveBeenCalledWith("demo-project");
expect(logSpy).toHaveBeenCalledWith(" kb Settings for project 'demo-project'");
expect(logSpy).toHaveBeenCalledWith(" fn Settings for project 'demo-project'");
});
it("runSettingsSet without project updates global-only settings", async () => {

View File

@@ -171,8 +171,8 @@ function getSettingLabel(key: string): string {
* Run settings show command.
*
* Behavior:
* - `kb settings` shows global settings
* - `kb settings --project <name>` shows project settings for that project
* - `fn settings` shows global settings
* - `fn settings --project <name>` shows project settings for that project
*/
export async function runSettingsShow(projectName?: string): Promise<void> {
const project = projectName ? await resolveProject(projectName) : undefined;
@@ -182,8 +182,8 @@ export async function runSettingsShow(projectName?: string): Promise<void> {
console.log();
console.log(project
? ` kb Settings for project '${project.projectName}'`
: " kb Global Settings");
? ` fn Settings for project '${project.projectName}'`
: " fn Global Settings");
console.log(" " + "─".repeat(50));
const settingGroups = [

View File

@@ -282,7 +282,7 @@ describe("project-aware task command behavior", () => {
const init = vi.fn();
vi.mocked(resolveProject).mockRejectedValueOnce(
new Error("No kb project found in current directory. Use --project or run from a project directory.")
new Error("No fn project found in current directory. Use --project or run from a project directory.")
);
(TaskStore as unknown as ReturnType<typeof vi.fn>).mockImplementation((projectPath: string) => ({
@@ -661,11 +661,11 @@ describe("project-aware task command behavior", () => {
it("surfaces project resolution failures from shared context when project flag is explicit", async () => {
vi.mocked(resolveProject).mockRejectedValueOnce(
new Error("Project 'demo-project' not found. Run 'kb project list' to see registered projects.")
new Error("Project 'demo-project' not found. Run 'fn project list' to see registered projects.")
);
await expect(runTaskList("demo-project")).rejects.toThrow(
"Project 'demo-project' not found. Run 'kb project list' to see registered projects."
"Project 'demo-project' not found. Run 'fn project list' to see registered projects."
);
});
});

View File

@@ -158,7 +158,7 @@ export async function runTaskList(projectName?: string) {
const tasks = await store.listTasks();
if (tasks.length === 0) {
console.log("\n No tasks yet. Create one with: kb task create\n");
console.log("\n No tasks yet. Create one with: fn task create\n");
process.exit(0);
}

View File

@@ -1492,7 +1492,7 @@ export default function kbExtension(pi: ExtensionAPI) {
name: "kb_feature_link_task",
label: "KB: Link Feature to Task",
description:
"Link a feature to a kb task for implementation. " +
"Link a feature to a fn task for implementation. " +
"Updates the feature status to 'triaged' and associates it with the task.",
promptSnippet: "Link a feature to a task",
promptGuidelines: [

View File

@@ -184,10 +184,10 @@ export async function detectProjectFromCwd(
// Walk up the directory tree
while (true) {
// Check for kb database
// Check for fn database
const kbPath = resolve(currentDir, ".fusion", "fusion.db");
if (existsSync(kbPath)) {
// Found a kb project - check if it's registered
// Found a fn project - check if it's registered
const project = await central.getProjectByPath(currentDir);
if (project) {
return project;

View File

@@ -245,7 +245,7 @@ export async function resolveProject(options: ResolveOptions = {}): Promise<Reso
// 4. Has .fusion/ but not registered
if (interactive) {
console.log(`\n Found kb project at ${kbDir} but it's not registered.`);
console.log(`\n Found fn project at ${kbDir} but it's not registered.`);
const shouldRegister = await promptConfirm("Register this project now?", true);
if (shouldRegister) {
@@ -284,7 +284,7 @@ export async function resolveProject(options: ResolveOptions = {}): Promise<Reso
}
} else {
throw new ProjectResolutionError(
`Found kb project at ${kbDir} but it's not registered.\n\n` +
`Found fn project at ${kbDir} but it's not registered.\n\n` +
"Run `fn project add " + kbDir + "` to register it, or use --project <name>.",
"NOT_REGISTERED",
{ directory: kbDir }
@@ -301,7 +301,7 @@ export async function resolveProject(options: ResolveOptions = {}): Promise<Reso
"No projects registered.\n\n" +
"To get started:\n" +
" 1. Navigate to your project directory\n" +
" 2. Run `fn init` to initialize kb\n" +
" 2. Run `fn init` to initialize fn\n" +
" 3. Run `fn project add .` to register it\n" +
"\nOr: `fn project add <path>` to register from anywhere.",
"NO_PROJECTS"
@@ -450,7 +450,7 @@ export async function isProjectNameTaken(
}
/**
* Validate that a path contains an initialized kb project (.fusion/ directory exists).
* Validate that a path contains an initialized fn project (.fusion/ directory exists).
*/
export function isKbProject(path: string): boolean {
const kbPath = resolve(path, ".fusion");
@@ -557,14 +557,14 @@ export async function registerProjectInteractive(
if (!isKbProject(absPath)) {
if (interactive) {
console.log(`\n No .fusion/ directory found in ${absPath}`);
const shouldInit = await promptConfirm("Initialize kb here first?", true);
const shouldInit = await promptConfirm("Initialize fn here first?", true);
if (shouldInit) {
// Initialize the project (create .fusion/)
const { TaskStore } = await import("@fusion/core");
const store = new TaskStore(absPath);
await store.init();
console.log(` ✓ Initialized kb at ${absPath}`);
console.log(` ✓ Initialized fn at ${absPath}`);
} else {
throw new ProjectResolutionError(
"Cannot register project without .fusion/ directory. Run `fn init` first.",