fix(cli): render actionable analysis errors

This commit is contained in:
morluto 2026-07-13 11:51:37 +00:00
parent eb38e4c402
commit af8c4efb4a
4 changed files with 32 additions and 26 deletions

View File

@ -6,6 +6,7 @@ import { silentLogger, type Logger } from "../logger.js";
import { createEvidence } from "../domain/evidence.js";
import type { NativeToolName } from "../contracts/nativeToolContracts.js";
import type { ArtifactToolName } from "../contracts/artifactToolContracts.js";
import { projectAnalysisError, type AnalysisError } from "../domain/errors.js";
const WORKFLOW_PROVIDER = {
id: "rea-workflow",
@ -47,8 +48,7 @@ export const runSessionStatus = async (
logger: Logger = silentLogger,
): Promise<JsonValue> => {
const config = parseConfig(process.env);
if (!config.ok)
return { error: config.error._tag, message: config.error.message };
if (!config.ok) return cliError(config.error);
const session = createBinarySession(config.value, logger);
try {
return session.status();
@ -64,13 +64,11 @@ const runAnalysis = async (
logger: Logger,
): Promise<JsonValue> => {
const config = parseConfig(process.env);
if (!config.ok)
return { error: config.error._tag, message: config.error.message };
if (!config.ok) return cliError(config.error);
const session = createBinarySession(config.value, logger);
try {
const opened = await session.open(path);
if (!opened.ok)
return { error: opened.error._tag, message: opened.error.message };
if (!opened.ok) return cliError(opened.error);
if (
tool === "binary_overview" ||
tool === "analyze_function" ||
@ -91,7 +89,7 @@ const runAnalysis = async (
limitations: ["Derived by an REA composed workflow."],
},
)
: { error: result.error._tag, message: result.error.message };
: cliError(result.error);
}
const result = await session.execute(tool, arguments_);
return result.ok
@ -107,8 +105,13 @@ const runAnalysis = async (
locations: result.value.locations,
},
)
: { error: result.error._tag, message: result.error.message };
: cliError(result.error);
} finally {
await session.close();
}
};
const cliError = (error: AnalysisError): JsonValue => ({
error: "Analysis failed",
...projectAnalysisError(error),
});

View File

@ -18,6 +18,7 @@ import { parseConfig } from "./config.js";
import { importReferenceSource } from "./application/ReferenceSourceImport.js";
import { registerEvidenceCommands } from "./cliEvidenceCommands.js";
import { registerProcessCommands } from "./cliProcessCommands.js";
import { projectAnalysisError } from "./domain/errors.js";
/**
* Build the one-shot Incur CLI without starting Hopper at import time.
@ -374,7 +375,10 @@ const registerReferenceSourceCommand = (
logCliCommand(logger, "import-reference-source", async () => {
const config = parseConfig(process.env);
if (!config.ok)
return { error: config.error._tag, message: config.error.message };
return {
error: "Import failed",
...projectAnalysisError(config.error),
};
const imported = await importReferenceSource({
root: args.root,
caller: "rea-cli",

View File

@ -8,6 +8,8 @@ import {
import { parseConfig } from "./config.js";
import { logCliCommand } from "./cliLogging.js";
import type { Logger } from "./logger.js";
import type { JsonValue } from "./domain/jsonValue.js";
import { projectAnalysisError, type AnalysisError } from "./domain/errors.js";
/** Register filesystem-gated Evidence v2 commands. */
export const registerEvidenceCommands = (
@ -22,15 +24,12 @@ export const registerEvidenceCommands = (
run: ({ args }) =>
logCliCommand(logger, "evidence-import", async () => {
const config = parseConfig(process.env);
if (!config.ok)
return { error: config.error._tag, message: config.error.message };
if (!config.ok) return cliError(config.error);
const imported = await importEvidenceBundleCommand(
args.path,
config.value.evidenceFilePolicy,
);
return imported.ok
? imported.value
: { error: imported.error._tag, message: imported.error.message };
return imported.ok ? imported.value : cliError(imported.error);
}),
});
cli.command("evidence-export", {
@ -45,17 +44,14 @@ export const registerEvidenceCommands = (
run: ({ args, options }) =>
logCliCommand(logger, "evidence-export", async () => {
const config = parseConfig(process.env);
if (!config.ok)
return { error: config.error._tag, message: config.error.message };
if (!config.ok) return cliError(config.error);
const exported = await exportEvidenceBundleCommand(
args.source,
args.output,
options.overwrite,
config.value.evidenceFilePolicy,
);
return exported.ok
? exported.value
: { error: exported.error._tag, message: exported.error.message };
return exported.ok ? exported.value : cliError(exported.error);
}),
});
cli.command("compare", {
@ -72,8 +68,7 @@ export const registerEvidenceCommands = (
run: ({ args, options }) =>
logCliCommand(logger, "compare", async () => {
const config = parseConfig(process.env);
if (!config.ok)
return { error: config.error._tag, message: config.error.message };
if (!config.ok) return cliError(config.error);
const compared = await compareEvidenceBundlesCommand({
leftPath: args.left,
rightPath: args.right,
@ -81,9 +76,12 @@ export const registerEvidenceCommands = (
limit: options.limit,
policy: config.value.evidenceFilePolicy,
});
return compared.ok
? compared.value
: { error: compared.error._tag, message: compared.error.message };
return compared.ok ? compared.value : cliError(compared.error);
}),
});
};
const cliError = (error: AnalysisError): JsonValue => ({
error: "Analysis failed",
...projectAnalysisError(error),
});

View File

@ -12,6 +12,7 @@ import { parseConfig } from "./config.js";
import { createBinarySession } from "./application/runtime.js";
import { createServer } from "./server/createServer.js";
import { createLogger } from "./logger.js";
import { projectAnalysisError } from "./domain/errors.js";
/**
* Start the long-lived MCP adapter and install idempotent shutdown handlers.
@ -21,7 +22,7 @@ import { createLogger } from "./logger.js";
export const run = async (): Promise<number> => {
const config = parseConfig(process.env);
if (!config.ok) {
process.stderr.write(`${config.error._tag}: ${config.error.message}\n`);
process.stderr.write(`${projectAnalysisError(config.error).message}\n`);
return 1;
}
const logger = createLogger("mcp", config.value.logLevel);
@ -38,7 +39,7 @@ export const run = async (): Promise<number> => {
{ errorTag: opened.error._tag },
"Initial target failed to open",
);
process.stderr.write(`${opened.error._tag}: ${opened.error.message}\n`);
process.stderr.write(`${projectAnalysisError(opened.error).message}\n`);
return 1;
}
}