import { resolve, join, dirname } from "node:path"; import { mkdir, writeFile } from "node:fs/promises"; import { discover } from "../../discovery/index"; import { buildOkfBundle, OKF_VERSION, type OkfFile } from "../../okf"; import { handleExplain } from "../mcp/tools/explain"; import { formatError, formatSuccess } from "../format"; import type { CommandContext } from "../registry"; const EXPLAIN_FORMATS = ["markdown", "json", "okf"] as const; type ExplainFormat = (typeof EXPLAIN_FORMATS)[number]; /** * `chant explain [path] [--format markdown|json|okf] [-o ]` — the CLI * path of the MCP `explain` tool (#1058): a structured summary of every * discovered entity. `--format okf` emits an OKF v0.2 knowledge bundle * instead — one markdown concept per entity plus an `index.md` — written as a * directory tree under `-o `, or printed as JSON (path → content) when * no output directory is given. */ export async function runExplain(ctx: CommandContext): Promise { const { args } = ctx; const format = (args.format || "markdown") as ExplainFormat; if (!EXPLAIN_FORMATS.includes(format)) { console.error(formatError({ message: `Invalid --format: ${format}. Expected one of ${EXPLAIN_FORMATS.join(", ")}.`, })); return 1; } const projectPath = resolve(args.path === "." ? "." : args.path); if (format === "okf") { const result = await discover(projectPath); for (const err of result.errors) console.error(formatError({ message: err.message })); const bundle = buildOkfBundle(result, projectPath); if (args.output) { await writeBundle(bundle, resolve(args.output)); console.log(formatSuccess(`Wrote OKF v${OKF_VERSION} bundle: ${bundle.length} file(s) under ${args.output}`)); } else { console.log(JSON.stringify({ okf_version: OKF_VERSION, files: Object.fromEntries(bundle.map((f) => [f.path, f.content])), }, null, 2)); } return result.errors.length > 0 ? 1 : 0; } const summary = await handleExplain({ path: projectPath, format }); console.log(typeof summary === "string" ? summary : JSON.stringify(summary, null, 2)); return 0; } /** Write the bundle's files under `outDir`, creating subdirectories as needed. */ async function writeBundle(bundle: OkfFile[], outDir: string): Promise { for (const file of bundle) { const target = join(outDir, file.path); await mkdir(dirname(target), { recursive: true }); await writeFile(target, file.content, "utf-8"); } }