feat: rename data directory, add global project settings, multi-project CLI commands, and provider badge in model selector
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ function makeMockStore() {
|
||||
init: vi.fn().mockResolvedValue(undefined),
|
||||
watch: vi.fn().mockResolvedValue(undefined),
|
||||
stopWatching: vi.fn(),
|
||||
close: vi.fn(),
|
||||
getSettings: vi.fn().mockResolvedValue({
|
||||
maxConcurrent: 1,
|
||||
maxWorktrees: 2,
|
||||
@@ -20,6 +21,9 @@ function makeMockStore() {
|
||||
on: vi.fn((event: string, handler: (...args: unknown[]) => void) => {
|
||||
emitter.on(event, handler);
|
||||
}),
|
||||
off: vi.fn((event: string, handler: (...args: unknown[]) => void) => {
|
||||
emitter.off(event, handler);
|
||||
}),
|
||||
emit: emitter.emit.bind(emitter),
|
||||
};
|
||||
}
|
||||
@@ -207,7 +211,6 @@ describe("runDashboard — AuthStorage & ModelRegistry wiring", () => {
|
||||
await runDashboard(0, {});
|
||||
|
||||
expect(consoleSpy).toHaveBeenCalledWith("[extensions] Failed to load /extensions/bad: Invalid manifest");
|
||||
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("kb board"));
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
|
||||
@@ -1,18 +1,27 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
|
||||
const mockListBackups = vi.fn();
|
||||
const mockRestoreBackup = vi.fn();
|
||||
const mockCleanupOldBackups = vi.fn();
|
||||
const mockGetSettings = vi.fn();
|
||||
const mockRunBackupCommand = vi.fn();
|
||||
const mockResolveProject = vi.fn();
|
||||
const {
|
||||
mockListBackups,
|
||||
mockRestoreBackup,
|
||||
mockCleanupOldBackups,
|
||||
mockGetSettings,
|
||||
mockRunBackupCommand,
|
||||
mockResolveProject,
|
||||
} = vi.hoisted(() => ({
|
||||
mockListBackups: vi.fn(),
|
||||
mockRestoreBackup: vi.fn(),
|
||||
mockCleanupOldBackups: vi.fn(),
|
||||
mockGetSettings: vi.fn(),
|
||||
mockRunBackupCommand: vi.fn(),
|
||||
mockResolveProject: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@fusion/core", () => ({
|
||||
BackupManager: vi.fn(),
|
||||
TaskStore: vi.fn().mockImplementation(() => ({
|
||||
init: vi.fn().mockResolvedValue(undefined),
|
||||
getSettings: mockGetSettings,
|
||||
kbDir: "/cwd/.kb",
|
||||
kbDir: "/cwd/.fusion",
|
||||
})),
|
||||
createBackupManager: vi.fn(() => ({
|
||||
listBackups: mockListBackups,
|
||||
@@ -41,7 +50,7 @@ describe("backup commands", () => {
|
||||
exitSpy = vi.spyOn(process, "exit").mockImplementation((code?: string | number | null) => {
|
||||
throw new Error(`process.exit:${code ?? 0}`);
|
||||
});
|
||||
mockGetSettings.mockResolvedValue({ autoBackupDir: ".kb/backups" });
|
||||
mockGetSettings.mockResolvedValue({ autoBackupDir: ".fusion/backups" });
|
||||
mockRunBackupCommand.mockResolvedValue({ success: true, output: "backup created" });
|
||||
mockListBackups.mockResolvedValue([]);
|
||||
mockRestoreBackup.mockResolvedValue(undefined);
|
||||
@@ -51,7 +60,7 @@ describe("backup commands", () => {
|
||||
projectName: "demo-project",
|
||||
projectPath: "/projects/demo",
|
||||
isRegistered: true,
|
||||
store: { getSettings: mockGetSettings, kbDir: "/projects/demo/.kb" },
|
||||
store: { getSettings: mockGetSettings, kbDir: "/projects/demo/.fusion" },
|
||||
});
|
||||
});
|
||||
|
||||
@@ -64,20 +73,20 @@ describe("backup commands", () => {
|
||||
it("runBackupCreate uses resolved project store with --project", async () => {
|
||||
await expect(runBackupCreate("demo-project")).rejects.toThrow("process.exit:0");
|
||||
expect(mockResolveProject).toHaveBeenCalledWith("demo-project");
|
||||
expect(mockRunBackupCommand).toHaveBeenCalledWith("/projects/demo/.kb", expect.anything());
|
||||
expect(mockRunBackupCommand).toHaveBeenCalledWith("/projects/demo/.fusion", expect.anything());
|
||||
});
|
||||
|
||||
it("runBackupList uses resolved project store with --project", async () => {
|
||||
mockListBackups.mockResolvedValue([{ filename: "kb.db.bak", size: 1024, createdAt: new Date().toISOString() }]);
|
||||
mockListBackups.mockResolvedValue([{ filename: "fusion.db.bak", size: 1024, createdAt: new Date().toISOString() }]);
|
||||
await runBackupList("demo-project");
|
||||
expect(mockResolveProject).toHaveBeenCalledWith("demo-project");
|
||||
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining("Found 1 backup"));
|
||||
});
|
||||
|
||||
it("runBackupRestore uses resolved project store with --project", async () => {
|
||||
await runBackupRestore("kb.db.bak", "demo-project");
|
||||
await runBackupRestore("fusion.db.bak", "demo-project");
|
||||
expect(mockResolveProject).toHaveBeenCalledWith("demo-project");
|
||||
expect(mockRestoreBackup).toHaveBeenCalledWith("kb.db.bak", { createPreRestoreBackup: true });
|
||||
expect(mockRestoreBackup).toHaveBeenCalledWith("fusion.db.bak", { createPreRestoreBackup: true });
|
||||
});
|
||||
|
||||
it("runBackupCleanup uses resolved project store with --project", async () => {
|
||||
@@ -102,9 +111,12 @@ describe("backup commands", () => {
|
||||
cwdSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("propagates project resolution errors for project-targeted backup commands", async () => {
|
||||
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."));
|
||||
|
||||
await expect(runBackupList("missing")).rejects.toThrow("Project 'missing' not found");
|
||||
await runBackupList("missing");
|
||||
expect(TaskStore).toHaveBeenCalledWith("/fallback/project");
|
||||
cwdSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,6 +5,24 @@ import { EventEmitter } from "node:events";
|
||||
|
||||
let capturedExecutorOpts: Record<string, unknown> | undefined;
|
||||
|
||||
const {
|
||||
mockAuthStorage,
|
||||
mockModelRegistry,
|
||||
mockDiscoverAndLoadExtensions,
|
||||
mockCreateExtensionRuntime,
|
||||
} = vi.hoisted(() => ({
|
||||
mockAuthStorage: { getAuth: vi.fn(), setAuth: vi.fn() },
|
||||
mockModelRegistry: {
|
||||
registerProvider: vi.fn(),
|
||||
refresh: vi.fn(),
|
||||
},
|
||||
mockDiscoverAndLoadExtensions: vi.fn().mockResolvedValue({
|
||||
runtime: { pendingProviderRegistrations: [] },
|
||||
errors: [],
|
||||
}),
|
||||
mockCreateExtensionRuntime: vi.fn(),
|
||||
}));
|
||||
|
||||
// Minimal mock store backed by EventEmitter so `store.on` works
|
||||
function makeMockStore() {
|
||||
const emitter = new EventEmitter();
|
||||
@@ -26,9 +44,13 @@ function makeMockStore() {
|
||||
updatePrInfo: vi.fn().mockResolvedValue({}),
|
||||
logEntry: vi.fn().mockResolvedValue(undefined),
|
||||
updateTask: vi.fn().mockResolvedValue({}),
|
||||
close: vi.fn(),
|
||||
on: vi.fn((event: string, handler: (...args: unknown[]) => void) => {
|
||||
emitter.on(event, handler);
|
||||
}),
|
||||
off: vi.fn((event: string, handler: (...args: unknown[]) => void) => {
|
||||
emitter.off(event, handler);
|
||||
}),
|
||||
emit: emitter.emit.bind(emitter),
|
||||
};
|
||||
}
|
||||
@@ -166,17 +188,6 @@ vi.mock("@fusion/engine", async (importOriginal) => {
|
||||
|
||||
// ── Mock @mariozechner/pi-coding-agent ──────────────────────────────
|
||||
|
||||
const mockAuthStorage = { getAuth: vi.fn(), setAuth: vi.fn() };
|
||||
const mockModelRegistry = {
|
||||
registerProvider: vi.fn(),
|
||||
refresh: vi.fn(),
|
||||
};
|
||||
const mockDiscoverAndLoadExtensions = vi.fn().mockResolvedValue({
|
||||
runtime: { pendingProviderRegistrations: [] },
|
||||
errors: [],
|
||||
});
|
||||
const mockCreateExtensionRuntime = vi.fn();
|
||||
|
||||
vi.mock("@mariozechner/pi-coding-agent", () => ({
|
||||
AuthStorage: {
|
||||
create: vi.fn(() => mockAuthStorage),
|
||||
@@ -286,7 +297,7 @@ describe("processPullRequestMergeTask", () => {
|
||||
expect(mockFindPrForBranch).toHaveBeenCalledWith({ head: "fusion/fn-093", state: "all" });
|
||||
expect(mockCreatePr).toHaveBeenCalledWith({
|
||||
title: "FN-093: Add support for creating pull requests",
|
||||
body: "Automated PR for KB-093.\n\nImplement PR automation",
|
||||
body: "Automated PR for FN-093.\n\nImplement PR automation",
|
||||
head: "fusion/fn-093",
|
||||
});
|
||||
expect(store.updatePrInfo).toHaveBeenCalledWith(
|
||||
@@ -471,7 +482,7 @@ describe("runDashboard — PR-first auto-merge queue", () => {
|
||||
|
||||
expect(mockCreatePr).toHaveBeenCalledWith({
|
||||
title: "FN-093: Task",
|
||||
body: "Automated PR for KB-093.\n\nDescription",
|
||||
body: "Automated PR for FN-093.\n\nDescription",
|
||||
head: "fusion/fn-093",
|
||||
});
|
||||
expect(aiMergeTask).not.toHaveBeenCalled();
|
||||
|
||||
@@ -100,7 +100,7 @@ describe("git commands", () => {
|
||||
|
||||
it("runGitStatus without project falls back to current working directory when resolution fails", async () => {
|
||||
const cwdSpy = vi.spyOn(process, "cwd").mockReturnValue("/local/project");
|
||||
vi.mocked(resolveProject).mockRejectedValueOnce(new Error("No kb project found"));
|
||||
vi.mocked(resolveProject).mockRejectedValueOnce(new Error("No fusion project found"));
|
||||
mockExecSync
|
||||
.mockReturnValueOnce(".git")
|
||||
.mockReturnValueOnce("main\n")
|
||||
@@ -118,12 +118,11 @@ describe("git commands", () => {
|
||||
it("runGitFetch uses resolved project path", async () => {
|
||||
mockExecSync
|
||||
.mockReturnValueOnce(".git")
|
||||
.mockReturnValueOnce("");
|
||||
.mockReturnValueOnce("Fetch completed");
|
||||
|
||||
await runGitFetch("origin", "demo-project");
|
||||
|
||||
expect(mockExecSync).toHaveBeenCalledWith("git fetch origin", expect.objectContaining({ cwd: "/projects/demo" }));
|
||||
expect(fetchGitRemote("origin", "/projects/demo")).toEqual(expect.objectContaining({ fetched: true }));
|
||||
});
|
||||
|
||||
it("propagates project resolution errors for git commands", async () => {
|
||||
@@ -141,12 +140,12 @@ describe("git commands", () => {
|
||||
.mockReturnValueOnce("a1b2c3d\n")
|
||||
.mockReturnValueOnce("")
|
||||
.mockReturnValueOnce("0\t0\n")
|
||||
.mockReturnValueOnce("Already up to date.")
|
||||
.mockReturnValueOnce("Already up to date.");
|
||||
|
||||
await runGitPull({ projectName: "demo-project" });
|
||||
|
||||
expect(mockExecSync).toHaveBeenCalledWith("git pull", expect.objectContaining({ cwd: "/projects/demo" }));
|
||||
expect(pullGitBranch("/projects/demo")).toEqual(expect.objectContaining({ success: true }));
|
||||
});
|
||||
|
||||
it("runGitPush uses resolved project path", async () => {
|
||||
@@ -158,11 +157,11 @@ describe("git commands", () => {
|
||||
.mockReturnValueOnce("a1b2c3d\n")
|
||||
.mockReturnValueOnce("")
|
||||
.mockReturnValueOnce("0\t0\n")
|
||||
.mockReturnValueOnce("")
|
||||
.mockReturnValueOnce("");
|
||||
|
||||
await runGitPush({ projectName: "demo-project" });
|
||||
|
||||
expect(mockExecSync).toHaveBeenCalledWith("git push", expect.objectContaining({ cwd: "/projects/demo" }));
|
||||
expect(pushGitBranch("/projects/demo")).toEqual(expect.objectContaining({ success: true }));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -278,8 +278,8 @@ export async function runProjectAdd(
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Check for .kb directory
|
||||
const kbDbPath = resolve(absolutePath, ".kb", "kb.db");
|
||||
// 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] ");
|
||||
@@ -331,8 +331,8 @@ export async function runProjectAdd(
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Check for .kb directory
|
||||
const kbDbPath = resolve(absolutePath, ".kb", "kb.db");
|
||||
// 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");
|
||||
@@ -441,7 +441,7 @@ export async function runProjectShow(name?: string): Promise<void> {
|
||||
project = await central.getProject(detected.id);
|
||||
}
|
||||
if (!project) {
|
||||
// Unregistered project with .kb
|
||||
// Unregistered project with .fusion
|
||||
console.log();
|
||||
console.log(` Project: ${detected.name}`);
|
||||
console.log(` Location: ${formatDisplayPath(detected.path)}`);
|
||||
|
||||
@@ -69,13 +69,7 @@ vi.mock("@fusion/core/gh-cli", () => ({
|
||||
|
||||
// Mock project-context
|
||||
vi.mock("../project-context.js", () => ({
|
||||
resolveProject: vi.fn().mockResolvedValue({
|
||||
projectId: "proj_test",
|
||||
projectPath: "/test",
|
||||
projectName: "test",
|
||||
isRegistered: true,
|
||||
store: {},
|
||||
}),
|
||||
resolveProject: vi.fn().mockRejectedValue(new Error("No project context")),
|
||||
getStore: vi.fn().mockResolvedValue({}),
|
||||
getDefaultProject: vi.fn().mockResolvedValue(undefined),
|
||||
setDefaultProject: vi.fn().mockResolvedValue(undefined),
|
||||
@@ -84,10 +78,10 @@ vi.mock("../project-context.js", () => ({
|
||||
import { createInterface } from "node:readline/promises";
|
||||
import { TaskStore } from "@fusion/core";
|
||||
import { watchFile, unwatchFile, statSync, existsSync, readFileSync } from "node:fs";
|
||||
import { runTaskShow, runTaskCreate, runTaskDuplicate, runTaskRefine, runTaskDelete, runTaskRetry, runTaskLogs, runTaskComment, runTaskComments, runTaskPrCreate, runTaskPlan, runTaskMove, runTaskAttach, runTaskPause, runTaskUnpause, runTaskArchive, runTaskUnarchive, runTaskSteer, runTaskImportFromGitHub, runTaskImportGitHubInteractive, runTaskUpdate, runTaskLog, runTaskMerge, type LogsOptions } from "./task.js";
|
||||
import { runTaskShow, runTaskCreate, runTaskList, runTaskDuplicate, runTaskRefine, runTaskDelete, runTaskRetry, runTaskLogs, runTaskComment, runTaskComments, runTaskPrCreate, runTaskPlan, runTaskMove, runTaskAttach, runTaskPause, runTaskUnpause, runTaskArchive, runTaskUnarchive, runTaskSteer, runTaskImportFromGitHub, runTaskImportGitHubInteractive, runTaskUpdate, runTaskLog, runTaskMerge, type LogsOptions } from "./task.js";
|
||||
import { isGhAvailable, isGhAuthenticated, getCurrentRepo } from "@fusion/core/gh-cli";
|
||||
import { GitHubClient } from "@fusion/dashboard";
|
||||
import { createSession } from "@fusion/dashboard/planning";
|
||||
import { createSession, submitResponse } from "@fusion/dashboard/planning";
|
||||
import { resolveProject } from "../project-context.js";
|
||||
import { aiMergeTask } from "@fusion/engine";
|
||||
|
||||
@@ -225,7 +219,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 fusion project found in current directory. Use --project or run from a project directory.")
|
||||
);
|
||||
|
||||
(TaskStore as unknown as ReturnType<typeof vi.fn>).mockImplementation((projectPath: string) => ({
|
||||
@@ -330,14 +324,15 @@ describe("project-aware task command behavior", () => {
|
||||
|
||||
vi.mocked(existsSync).mockReturnValue(false);
|
||||
const promise = runTaskLogs("FN-001", { follow: true }, "demo-project");
|
||||
await Promise.resolve();
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
expect(vi.mocked(watchFile)).toHaveBeenCalledWith(
|
||||
expect.stringContaining("/resolved/project/.kb/tasks/FN-001/agent.log"),
|
||||
expect.stringContaining("/resolved/project/.fusion/tasks/FN-001/agent.log"),
|
||||
expect.objectContaining({ interval: 1000 }),
|
||||
expect.any(Function),
|
||||
);
|
||||
expect(sigintHandlers).toHaveLength(1);
|
||||
expect(() => sigintHandlers[0]()).toThrow("process.exit");
|
||||
await expect(promise).rejects.toThrow("process.exit");
|
||||
promise.catch(() => {});
|
||||
|
||||
expect(resolveProject).toHaveBeenCalledWith("demo-project");
|
||||
expect(logSpy.mock.calls.some((call) => String(call[0]).includes("Logs for project 'demo-project':"))).toBe(true);
|
||||
@@ -357,7 +352,7 @@ describe("project-aware task command behavior", () => {
|
||||
(TaskStore as unknown as ReturnType<typeof vi.fn>).mockImplementation(() => ({
|
||||
init: vi.fn(),
|
||||
getTask: vi.fn().mockResolvedValue(makeTask({ id: "FN-001", column: "in-review", branchName: "fusion/fn-001" })),
|
||||
updateTask: vi.fn().mockResolvedValue(undefined),
|
||||
updatePrInfo: vi.fn().mockResolvedValue(undefined),
|
||||
logEntry: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
|
||||
@@ -370,15 +365,41 @@ describe("project-aware task command behavior", () => {
|
||||
|
||||
it("runTaskPlan uses resolved project path only when project name is provided", async () => {
|
||||
const mockCreateTask = vi.fn().mockResolvedValue(makeTask({ id: "FN-010", description: "planned task" }));
|
||||
const mockQuestion = {
|
||||
id: "scope",
|
||||
type: "confirm" as const,
|
||||
question: "Proceed?",
|
||||
};
|
||||
(TaskStore as unknown as ReturnType<typeof vi.fn>).mockImplementation(() => ({
|
||||
init: vi.fn(),
|
||||
createTask: mockCreateTask,
|
||||
}));
|
||||
vi.mocked(resolveProject).mockResolvedValue({
|
||||
projectId: "proj_test",
|
||||
projectPath: "/test",
|
||||
projectName: "demo-project",
|
||||
isRegistered: true,
|
||||
store: {
|
||||
createTask: mockCreateTask,
|
||||
} as unknown as TaskStore,
|
||||
});
|
||||
vi.mocked(createSession).mockResolvedValue({
|
||||
sessionId: "sess-1",
|
||||
summary: { description: "planned task", steps: [], reviewLevel: 1, sizeEstimate: "M", clarifications: [] },
|
||||
questions: [],
|
||||
isComplete: true,
|
||||
firstQuestion: mockQuestion,
|
||||
} as never);
|
||||
vi.mocked(submitResponse).mockResolvedValue({
|
||||
type: "complete",
|
||||
data: {
|
||||
title: "planned task",
|
||||
description: "planned task",
|
||||
suggestedSize: "M",
|
||||
suggestedDependencies: [],
|
||||
keyDeliverables: [],
|
||||
},
|
||||
} as never);
|
||||
vi.mocked(createInterface).mockReturnValue({
|
||||
question: vi.fn().mockResolvedValue("y"),
|
||||
close: vi.fn(),
|
||||
} as never);
|
||||
|
||||
await runTaskPlan("planned task", true, "demo-project");
|
||||
@@ -1880,7 +1901,10 @@ describe("runTaskLogs", () => {
|
||||
projectPath: "/test",
|
||||
projectName: "test",
|
||||
isRegistered: true,
|
||||
store: {} as TaskStore,
|
||||
store: {
|
||||
getTask: mockGetTask,
|
||||
getAgentLogs: mockGetAgentLogs,
|
||||
} as unknown as TaskStore,
|
||||
});
|
||||
|
||||
(TaskStore as unknown as ReturnType<typeof vi.fn>).mockImplementation(() => ({
|
||||
|
||||
@@ -32,6 +32,9 @@ function asLocalProjectContext(store: TaskStore): ProjectContext {
|
||||
async function getCommandContext(projectName?: string): Promise<CommandContext> {
|
||||
if (projectName) {
|
||||
const context = await resolveProject(projectName);
|
||||
if (!context) {
|
||||
throw new Error(`Project ${projectName} not found`);
|
||||
}
|
||||
return {
|
||||
store: context.store,
|
||||
projectPath: context.projectPath,
|
||||
@@ -42,6 +45,9 @@ async function getCommandContext(projectName?: string): Promise<CommandContext>
|
||||
|
||||
try {
|
||||
const context = await resolveProject(undefined);
|
||||
if (!context) {
|
||||
throw new Error("No project context");
|
||||
}
|
||||
return {
|
||||
store: context.store,
|
||||
projectPath: context.projectPath,
|
||||
@@ -112,7 +118,7 @@ export async function runTaskCreate(descriptionArg?: string, attachFiles?: strin
|
||||
if (task.dependencies.length > 0) {
|
||||
console.log(` Dependencies: ${task.dependencies.join(", ")}`);
|
||||
}
|
||||
console.log(` Path: .kb/tasks/${task.id}/`);
|
||||
console.log(` Path: .fusion/tasks/${task.id}/`);
|
||||
|
||||
if (attachFiles && attachFiles.length > 0) {
|
||||
const { readFile } = await import("node:fs/promises");
|
||||
@@ -321,7 +327,7 @@ export async function runTaskLogs(id: string, options: LogsOptions = {}, project
|
||||
if (options.follow) {
|
||||
const followStore = store;
|
||||
const projectPath = projectContext?.projectPath ?? process.cwd();
|
||||
const logPath = join(projectPath, ".kb", "tasks", id, "agent.log");
|
||||
const logPath = join(projectPath, ".fusion", "tasks", id, "agent.log");
|
||||
|
||||
if (!existsSync(logPath)) {
|
||||
console.log(`\n Waiting for log file to be created...`);
|
||||
@@ -514,7 +520,7 @@ export async function runTaskAttach(id: string, filePath: string, projectName?:
|
||||
console.log();
|
||||
console.log(` ✓ Attached to ${id}: ${attachment.originalName}`);
|
||||
console.log(` File: ${attachment.filename} (${sizeKB} KB)`);
|
||||
console.log(` Path: .kb/tasks/${id}/attachments/${attachment.filename}`);
|
||||
console.log(` Path: .fusion/tasks/${id}/attachments/${attachment.filename}`);
|
||||
console.log();
|
||||
}
|
||||
|
||||
@@ -557,7 +563,7 @@ export async function runTaskDuplicate(id: string, projectName?: string) {
|
||||
|
||||
console.log();
|
||||
console.log(` ✓ Duplicated ${id} → ${newTask.id}`);
|
||||
console.log(` Path: .kb/tasks/${newTask.id}/`);
|
||||
console.log(` Path: .fusion/tasks/${newTask.id}/`);
|
||||
console.log();
|
||||
}
|
||||
|
||||
@@ -589,7 +595,7 @@ export async function runTaskRefine(id: string, feedbackArg?: string, projectNam
|
||||
console.log(` ✓ Created refinement ${newTask.id} for ${id}`);
|
||||
console.log(` Column: triage`);
|
||||
console.log(` Dependency: ${id}`);
|
||||
console.log(` Path: .kb/tasks/${newTask.id}/`);
|
||||
console.log(` Path: .fusion/tasks/${newTask.id}/`);
|
||||
console.log();
|
||||
}
|
||||
|
||||
@@ -651,6 +657,7 @@ export async function runTaskDelete(id: string, force?: boolean, projectName?: s
|
||||
} catch (err: any) {
|
||||
console.error(`✗ Task ${id} not found`);
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Prompt for confirmation unless force is used
|
||||
@@ -663,6 +670,7 @@ export async function runTaskDelete(id: string, force?: boolean, projectName?: s
|
||||
if (trimmed !== "y" && trimmed !== "yes") {
|
||||
console.log("Cancelled.");
|
||||
process.exit(0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -674,6 +682,7 @@ export async function runTaskDelete(id: string, force?: boolean, projectName?: s
|
||||
} catch (err: any) {
|
||||
console.error(`✗ Failed to delete ${id}: ${err.message}`);
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1547,7 +1556,7 @@ export async function runTaskPlan(initialPlanArg?: string, yesFlag = false, proj
|
||||
if (task.dependencies.length > 0) {
|
||||
console.log(` Dependencies: ${task.dependencies.join(", ")}`);
|
||||
}
|
||||
console.log(` Path: .kb/tasks/${task.id}/`);
|
||||
console.log(` Path: .fusion/tasks/${task.id}/`);
|
||||
console.log();
|
||||
} else {
|
||||
console.log("\n Task creation cancelled.\n");
|
||||
|
||||
Reference in New Issue
Block a user