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 0b9486502b
commit 102ffe6e7d
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.",

View File

@@ -401,9 +401,9 @@ export async function syncBackupAutomation(
}
// Build the backup command
// The CLI command will be: kb backup --auto
// The CLI command will be: fn backup --auto
// The --auto flag indicates this is an automated run (can add special handling if needed)
const command = "kb backup --create";
const command = "fn backup --create";
if (existingSchedule) {
// Update existing schedule

View File

@@ -581,7 +581,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
/**
* Update global (user-level) settings in `~/.pi/fusion/settings.json`.
*
* These settings persist across all kb projects for the current user.
* These settings persist across all fn projects for the current user.
* Only fields defined in `GlobalSettings` are accepted.
*/
async updateGlobalSettings(patch: Partial<GlobalSettings>): Promise<Settings> {
@@ -1828,7 +1828,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
`Merge conflict merging '${branch}'. Resolve manually:\n` +
` cd ${this.rootDir}\n` +
` git merge --squash ${branch}\n` +
` # resolve conflicts, then: kb task move ${id} done`,
` # resolve conflicts, then: fn task move ${id} done`,
);
}

View File

@@ -280,7 +280,7 @@ export function fetchGlobalSettings(): Promise<GlobalSettings> {
return api<GlobalSettings>("/settings/global");
}
/** Update global (user-level) settings. These persist across all kb projects. */
/** Update global (user-level) settings. These persist across all fn projects. */
export function updateGlobalSettings(settings: Partial<GlobalSettings>): Promise<Settings> {
return api<Settings>("/settings/global", {
method: "PUT",
@@ -507,7 +507,7 @@ export function apiFetchGitHubIssues(
});
}
/** Import a specific GitHub issue as a kb task */
/** Import a specific GitHub issue as a fn task */
export function apiImportGitHubIssue(owner: string, repo: string, issueNumber: number): Promise<Task> {
return api<Task>("/github/issues/import", {
method: "POST",
@@ -525,7 +525,7 @@ export interface BatchImportResult {
retryAfter?: number;
}
/** Batch import multiple GitHub issues as kb tasks with throttling */
/** Batch import multiple GitHub issues as fn tasks with throttling */
export function apiBatchImportGitHubIssues(
owner: string,
repo: string,
@@ -562,7 +562,7 @@ export function apiFetchGitHubPulls(
});
}
/** Import a specific GitHub pull request as a kb review task */
/** Import a specific GitHub pull request as a fn review task */
export function apiImportGitHubPull(owner: string, repo: string, prNumber: number): Promise<Task> {
return api<Task>("/github/pulls/import", {
method: "POST",

View File

@@ -85,7 +85,7 @@ export function PrSection({
Pull Request
</h4>
<div className="pr-hint pr-hint--muted">
kb is creating a pull request automatically for this task.
fn is creating a pull request automatically for this task.
</div>
</div>
);
@@ -199,7 +199,7 @@ export function PrSection({
</div>
{automationStatus === "merging-pr" && (
<div className="pr-hint pr-hint--info">
kb is merging this pull request automatically.
fn is merging this pull request automatically.
</div>
)}
{automationStatus === "awaiting-pr-checks" && (
@@ -211,7 +211,7 @@ export function PrSection({
)}
{prInfo.status === "merged" && (
<div className="pr-hint pr-hint--info">
This PR is merged. kb will finish local cleanup and move the task to Done.
This PR is merged. fn will finish local cleanup and move the task to Done.
</div>
)}
<div className="pr-footer">

View File

@@ -22,7 +22,7 @@ export interface ProjectDetectionResultsProps {
* Displays detected projects with:
* - Checkboxes for selection
* - Editable names
* - Warnings for projects without kb database
* - Warnings for projects without fn database
* - Register Selected / Register All buttons
*/
export function ProjectDetectionResults({
@@ -202,7 +202,7 @@ export function ProjectDetectionResults({
{!hasKbDb && (
<div className="detection-result-warning">
<AlertCircle size={12} />
<span>No kb database found - will be initialized</span>
<span>No fn database found - will be initialized</span>
</div>
)}
</div>

View File

@@ -569,7 +569,7 @@ export function SettingsModal({
return (
<div className="settings-scope-banner settings-scope-global">
<span>🌐</span>
<span>These settings are shared across all your kb projects.</span>
<span>These settings are shared across all your fn projects.</span>
</div>
);
}
@@ -1464,7 +1464,7 @@ export function SettingsModal({
<option value="pull-request">Create, monitor, and merge a GitHub pull request</option>
</select>
<small>
Controls what happens after a task reaches In Review. Direct mode preserves kb&apos;s current local squash-merge behavior. Pull request mode keeps the task in In Review while kb waits for GitHub reviews and required checks before merging the PR.
Controls what happens after a task reaches In Review. Direct mode preserves fn&apos;s current local squash-merge behavior. Pull request mode keeps the task in In Review while fn waits for GitHub reviews and required checks before merging the PR.
</small>
</div>
<div className="form-group">

View File

@@ -105,7 +105,7 @@ describe("SettingsModal mobile adaptations", () => {
expect(getByText("These settings only affect this project.")).toBeTruthy();
await user.click(getByText("Appearance"));
expect(getByText("These settings are shared across all your kb projects.")).toBeTruthy();
expect(getByText("These settings are shared across all your fn projects.")).toBeTruthy();
});
it("contains required mobile settings CSS overrides", () => {

View File

@@ -40,7 +40,7 @@ const engineReady = initEngine();
// ── Constants ───────────────────────────────────────────────────────────────
/** System prompt for the AI agent that generates agent specifications */
export const AGENT_GENERATION_SYSTEM_PROMPT = `You are an agent specification generator for the kb task board system.
export const AGENT_GENERATION_SYSTEM_PROMPT = `You are an agent specification generator for the fn task board system.
Your job: given a user-provided role description, generate a complete agent specification suitable for creating an AI agent.

View File

@@ -55,7 +55,7 @@ const engineReady = initEngine();
// ── Constants ───────────────────────────────────────────────────────────────
/** Planning system prompt for the AI agent */
export const PLANNING_SYSTEM_PROMPT = `You are a planning assistant for the kb task board system.
export const PLANNING_SYSTEM_PROMPT = `You are a planning assistant for the fn task board system.
Your job: help users transform vague, high-level ideas into well-defined, actionable tasks.

View File

@@ -210,7 +210,7 @@ function unpersistSubtaskSession(sessionId: string): void {
_aiSessionStore.delete(sessionId);
}
export const SUBTASK_BREAKDOWN_PROMPT = `You are a task decomposition assistant for the kb task board system.
export const SUBTASK_BREAKDOWN_PROMPT = `You are a task decomposition assistant for the fn task board system.
Analyze the user's task description and break it down into 2-5 smaller, independently executable subtasks.