mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-10 19:33:13 +02:00
feat(memory-wiki): add wiki doctor diagnostics
This commit is contained in:
@@ -19,10 +19,12 @@ describe("memory-wiki cli", () => {
|
||||
vi.spyOn(process.stdout, "write").mockImplementation(
|
||||
(() => true) as typeof process.stdout.write,
|
||||
);
|
||||
process.exitCode = undefined;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
process.exitCode = undefined;
|
||||
});
|
||||
|
||||
it("registers apply synthesis and writes a synthesis page", async () => {
|
||||
@@ -123,4 +125,24 @@ cli note
|
||||
expect(parsed.frontmatter).not.toHaveProperty("confidence");
|
||||
expect(parsed.body).toContain("cli note");
|
||||
});
|
||||
|
||||
it("runs wiki doctor and sets a non-zero exit code when warnings exist", async () => {
|
||||
const rootDir = await fs.mkdtemp(path.join(os.tmpdir(), "memory-wiki-cli-"));
|
||||
tempDirs.push(rootDir);
|
||||
const config = resolveMemoryWikiConfig(
|
||||
{
|
||||
vault: { path: rootDir },
|
||||
obsidian: { enabled: true, useOfficialCli: true },
|
||||
},
|
||||
{ homedir: "/Users/tester" },
|
||||
);
|
||||
const program = new Command();
|
||||
program.name("test");
|
||||
registerWikiCli(program, config);
|
||||
await fs.rm(rootDir, { recursive: true, force: true });
|
||||
|
||||
await program.parseAsync(["wiki", "doctor", "--json"], { from: "user" });
|
||||
|
||||
expect(process.exitCode).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,13 +16,22 @@ import {
|
||||
} from "./obsidian.js";
|
||||
import { getMemoryWikiPage, searchMemoryWiki } from "./query.js";
|
||||
import { syncMemoryWikiImportedSources } from "./source-sync.js";
|
||||
import { renderMemoryWikiStatus, resolveMemoryWikiStatus } from "./status.js";
|
||||
import {
|
||||
buildMemoryWikiDoctorReport,
|
||||
renderMemoryWikiDoctor,
|
||||
renderMemoryWikiStatus,
|
||||
resolveMemoryWikiStatus,
|
||||
} from "./status.js";
|
||||
import { initializeMemoryWikiVault } from "./vault.js";
|
||||
|
||||
type WikiStatusCommandOptions = {
|
||||
json?: boolean;
|
||||
};
|
||||
|
||||
type WikiDoctorCommandOptions = {
|
||||
json?: boolean;
|
||||
};
|
||||
|
||||
type WikiInitCommandOptions = {
|
||||
json?: boolean;
|
||||
};
|
||||
@@ -149,6 +158,24 @@ export async function runWikiStatus(params: {
|
||||
return status;
|
||||
}
|
||||
|
||||
export async function runWikiDoctor(params: {
|
||||
config: ResolvedMemoryWikiConfig;
|
||||
appConfig?: OpenClawConfig;
|
||||
json?: boolean;
|
||||
stdout?: Pick<NodeJS.WriteStream, "write">;
|
||||
}) {
|
||||
await syncMemoryWikiImportedSources({ config: params.config, appConfig: params.appConfig });
|
||||
const report = buildMemoryWikiDoctorReport(await resolveMemoryWikiStatus(params.config));
|
||||
if (!report.healthy) {
|
||||
process.exitCode = 1;
|
||||
}
|
||||
writeOutput(
|
||||
params.json ? JSON.stringify(report, null, 2) : renderMemoryWikiDoctor(report),
|
||||
params.stdout,
|
||||
);
|
||||
return report;
|
||||
}
|
||||
|
||||
export async function runWikiInit(params: {
|
||||
config: ResolvedMemoryWikiConfig;
|
||||
json?: boolean;
|
||||
@@ -469,6 +496,14 @@ export function registerWikiCli(
|
||||
await runWikiStatus({ config, appConfig, json: opts.json });
|
||||
});
|
||||
|
||||
wiki
|
||||
.command("doctor")
|
||||
.description("Audit wiki vault setup and report actionable fixes")
|
||||
.option("--json", "Print JSON")
|
||||
.action(async (opts: WikiDoctorCommandOptions) => {
|
||||
await runWikiDoctor({ config, appConfig, json: opts.json });
|
||||
});
|
||||
|
||||
wiki
|
||||
.command("init")
|
||||
.description("Initialize the wiki vault layout")
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveMemoryWikiConfig } from "./config.js";
|
||||
import { renderMemoryWikiStatus, resolveMemoryWikiStatus } from "./status.js";
|
||||
import {
|
||||
buildMemoryWikiDoctorReport,
|
||||
renderMemoryWikiDoctor,
|
||||
renderMemoryWikiStatus,
|
||||
resolveMemoryWikiStatus,
|
||||
} from "./status.js";
|
||||
|
||||
describe("resolveMemoryWikiStatus", () => {
|
||||
it("reports missing vault and missing requested obsidian cli", async () => {
|
||||
@@ -83,3 +88,28 @@ describe("renderMemoryWikiStatus", () => {
|
||||
expect(rendered).toContain("Wiki vault has not been initialized yet.");
|
||||
});
|
||||
});
|
||||
|
||||
describe("memory wiki doctor", () => {
|
||||
it("builds actionable fixes from status warnings", async () => {
|
||||
const config = resolveMemoryWikiConfig(
|
||||
{
|
||||
vault: { path: "/tmp/wiki" },
|
||||
obsidian: { enabled: true, useOfficialCli: true },
|
||||
},
|
||||
{ homedir: "/Users/tester" },
|
||||
);
|
||||
|
||||
const status = await resolveMemoryWikiStatus(config, {
|
||||
pathExists: async () => false,
|
||||
resolveCommand: async () => null,
|
||||
});
|
||||
const report = buildMemoryWikiDoctorReport(status);
|
||||
const rendered = renderMemoryWikiDoctor(report);
|
||||
|
||||
expect(report.healthy).toBe(false);
|
||||
expect(report.warningCount).toBe(2);
|
||||
expect(report.fixes.map((fix) => fix.code)).toEqual(["vault-missing", "obsidian-cli-missing"]);
|
||||
expect(rendered).toContain("Suggested fixes:");
|
||||
expect(rendered).toContain("openclaw wiki init");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -35,6 +35,18 @@ export type MemoryWikiStatus = {
|
||||
warnings: MemoryWikiStatusWarning[];
|
||||
};
|
||||
|
||||
export type MemoryWikiDoctorFix = {
|
||||
code: MemoryWikiStatusWarning["code"];
|
||||
message: string;
|
||||
};
|
||||
|
||||
export type MemoryWikiDoctorReport = {
|
||||
healthy: boolean;
|
||||
warningCount: number;
|
||||
status: MemoryWikiStatus;
|
||||
fixes: MemoryWikiDoctorFix[];
|
||||
};
|
||||
|
||||
type ResolveMemoryWikiStatusDeps = {
|
||||
pathExists?: (inputPath: string) => Promise<boolean>;
|
||||
resolveCommand?: (command: string) => Promise<string | null>;
|
||||
@@ -172,6 +184,30 @@ export async function resolveMemoryWikiStatus(
|
||||
};
|
||||
}
|
||||
|
||||
export function buildMemoryWikiDoctorReport(status: MemoryWikiStatus): MemoryWikiDoctorReport {
|
||||
const fixes = status.warnings.map((warning) => ({
|
||||
code: warning.code,
|
||||
message:
|
||||
warning.code === "vault-missing"
|
||||
? "Run `openclaw wiki init` to create the vault layout."
|
||||
: warning.code === "obsidian-cli-missing"
|
||||
? "Install the official Obsidian CLI or disable `obsidian.useOfficialCli`."
|
||||
: warning.code === "bridge-disabled"
|
||||
? "Enable `plugins.entries.memory-wiki.config.bridge.enabled` or switch vaultMode away from `bridge`."
|
||||
: warning.code === "unsafe-local-disabled"
|
||||
? "Enable `unsafeLocal.allowPrivateMemoryCoreAccess` or switch vaultMode away from `unsafe-local`."
|
||||
: warning.code === "unsafe-local-paths-missing"
|
||||
? "Add explicit `unsafeLocal.paths` entries before running unsafe-local imports."
|
||||
: "Disable private memory-core access unless you explicitly want unsafe-local mode.",
|
||||
}));
|
||||
return {
|
||||
healthy: status.warnings.length === 0,
|
||||
warningCount: status.warnings.length,
|
||||
status,
|
||||
fixes,
|
||||
};
|
||||
}
|
||||
|
||||
export function renderMemoryWikiStatus(status: MemoryWikiStatus): string {
|
||||
const lines = [
|
||||
`Wiki vault mode: ${status.vaultMode}`,
|
||||
@@ -192,3 +228,20 @@ export function renderMemoryWikiStatus(status: MemoryWikiStatus): string {
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
export function renderMemoryWikiDoctor(report: MemoryWikiDoctorReport): string {
|
||||
const lines = [
|
||||
report.healthy ? "Wiki doctor: healthy" : `Wiki doctor: ${report.warningCount} issue(s) found`,
|
||||
"",
|
||||
renderMemoryWikiStatus(report.status),
|
||||
];
|
||||
|
||||
if (report.fixes.length > 0) {
|
||||
lines.push("", "Suggested fixes:");
|
||||
for (const fix of report.fixes) {
|
||||
lines.push(`- ${fix.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user