Files
rea/src/cli/applicationCommands.ts
T
0ecf3f2438 perf(mcp): keep Playwright and type-only imports off MCP startup (#1691)
* fix(build): emit no runtime imports for type-only bindings

Under verbatimModuleSyntax, an import whose specifiers all carry inline
`type` modifiers compiles to `import {} from "..."`, a side-effect import
that still resolves and evaluates the module. 78 compiled modules carried
one, including `import {} from "playwright-core"` in the browser scenario
session, which loaded Playwright's module graph at MCP startup.

Use top-level `import type` for these bindings and enable oxlint's
typescript/no-import-type-side-effects rule so the pattern cannot return.

Refs #1685

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(mcp): load Playwright when a scenario launches, not at startup

The browser scenario and Electron active providers are constructed before
the stdio server serves, and both bound Playwright's launchers at module
load. Resolve `chromium` and `_electron` from playwright-core when a
capture opens instead, as the web module resolver already does.

MCP startup now resolves 1,312 modules (4,902 resolutions) instead of
1,598 (6,076) before reading the first request, and no longer loads
playwright-core. Provider construction, the tool catalog, and capability
inventory are unchanged.

A boundary test spawns the stdio server, initializes, lists tools, and
asserts playwright-core and isomorphic-git were not resolved; the CLI
probe now exercises the real launch path to prove detection.

Closes #1685

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 21:04:05 -07:00

203 lines
7.6 KiB
TypeScript

import { reconstructionCoverageEvaluationInputSchema } from "../domain/reconstructionCoverageInput.js";
import { Cli, z } from "incur";
import { projectAndroidApplicationEvidence } from "../application/android/AndroidApplicationService.js";
import { projectAppleApplicationEvidence } from "../application/apple/AppleApplicationService.js";
import {
compareApplicationVersionsEvidenceValidated,
compareJavaScriptExportShapesEvidenceValidated,
compareSourceToBundleEvidenceValidated,
traceApplicationFeatureEvidenceValidated,
} from "../application/javascript/JavaScriptApplicationWorkflowService.js";
import { traceJavaScriptSemanticsEvidenceValidated } from "../application/javascript/JavaScriptSemanticTraceService.js";
import { evaluateReconstructionCoverage } from "../application/ReconstructionCoverageService.js";
import { buildReconstructionObligationLedgerEvidenceValidated } from "../application/ReconstructionObligationLedgerService.js";
import { CLI_COMMANDS } from "../cliCommandNames.js";
import { parseCliJsonInput } from "../cliJsonInput.js";
import { logCliCommand } from "../cliLogging.js";
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
import type { AnalysisError } from "../domain/analysisErrorBase.js";
import { androidApplicationProjectionInputSchema } from "../domain/android/androidApplication.js";
import { appleApplicationProjectionInputSchema } from "../domain/apple/appleApplication.js";
import { jsonValueSchema, type JsonValue } from "../domain/jsonValue.js";
import type { Logger } from "pino";
import { traceApplicationFeatureInputSchema } from "../domain/javascript/javascriptFeatureTraceSchemas.js";
import { traceJavaScriptSemanticsInputSchema } from "../domain/javascript/javascriptSemanticTraceSchemas.js";
import { compareApplicationVersionsInputSchema } from "../domain/javascript/javascriptApplicationVersionComparisonSchemas.js";
import { compareSourceToBundleInputSchema } from "../domain/javascript/sourceToBundleComparisonSchemas.js";
import { compareJavaScriptExportShapesInputSchema } from "../domain/javascript/javascriptExportShapeComparisonSchemas.js";
import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js";
import { reconstructionObligationLedgerInputSchema } from "../domain/reconstructionObligationLedgerSchemas.js";
type CliInstance = ReturnType<typeof Cli.create>;
/** Register CLI equivalents of provider-neutral application graph workflows. */
export const registerApplicationCommands = (
cli: ReturnType<typeof Cli.create>,
logger: Logger,
): void => {
registerJsonCommand({
cli,
logger,
name: CLI_COMMANDS.traceApplicationFeature,
description:
"Trace a typed seed through authenticated application Evidence JSON",
inputSchema: traceApplicationFeatureInputSchema,
workflow: traceApplicationFeatureEvidenceValidated,
});
registerJsonCommand({
cli,
logger,
name: CLI_COMMANDS.traceJavaScriptSemantics,
description:
"Trace bounded static semantic relations through authenticated application Evidence",
inputSchema: traceJavaScriptSemanticsInputSchema,
workflow: traceJavaScriptSemanticsEvidenceValidated,
});
registerJsonCommand({
cli,
logger,
name: CLI_COMMANDS.compareApplicationVersions,
description:
"Compare two authenticated JavaScript Application Graph versions",
inputSchema: compareApplicationVersionsInputSchema,
workflow: compareApplicationVersionsEvidenceValidated,
});
registerJsonCommand({
cli,
logger,
name: CLI_COMMANDS.compareSourceToBundle,
description:
"Compare committed historical source with authenticated application Evidence",
inputSchema: compareSourceToBundleInputSchema,
workflow: compareSourceToBundleEvidenceValidated,
});
registerJsonCommand({
cli,
logger,
name: CLI_COMMANDS.compareJavaScriptExportShapes,
description:
"Compare exact static JavaScript export return shapes without execution",
inputSchema: compareJavaScriptExportShapesInputSchema,
workflow: compareJavaScriptExportShapesEvidenceValidated,
});
registerObligationLedgerCommand(cli, logger);
registerCoverageCommand(cli, logger);
registerJsonCommand({
cli,
logger,
name: CLI_COMMANDS.projectAndroidApplicationGraph,
description:
"Project authenticated APK inventory Evidence into an Android application graph",
inputSchema: androidApplicationProjectionInputSchema,
workflow: (input) => {
const result = projectAndroidApplicationEvidence(input);
return result.ok
? { ok: true, value: jsonValueSchema.parse(result.value) }
: result;
},
});
registerJsonCommand({
cli,
logger,
name: CLI_COMMANDS.projectAppleApplicationGraph,
description:
"Project authenticated IPA or macOS app inventory Evidence into an Apple application graph",
inputSchema: appleApplicationProjectionInputSchema,
workflow: (input) => {
const result = projectAppleApplicationEvidence(input);
return result.ok
? { ok: true, value: jsonValueSchema.parse(result.value) }
: result;
},
});
};
const registerObligationLedgerCommand = (
cli: CliInstance,
logger: Logger,
): void =>
registerJsonCommand({
cli,
logger,
name: CLI_COMMANDS.buildReconstructionObligationLedger,
description:
"Generate a deterministic Evidence-backed reconstruction obligation ledger page",
inputSchema: reconstructionObligationLedgerInputSchema,
workflow: (input) => {
const result =
buildReconstructionObligationLedgerEvidenceValidated(input);
return result.ok
? { ok: true, value: jsonValueSchema.parse(result.value) }
: result;
},
});
const registerCoverageCommand = (cli: CliInstance, logger: Logger): void =>
registerJsonCommand({
cli,
logger,
name: CLI_COMMANDS.evaluateReconstructionCoverage,
description: "Evaluate inline fail-closed reconstruction coverage",
inputSchema: reconstructionCoverageEvaluationInputSchema,
workflow: (input) => {
const result = evaluateReconstructionCoverage(input);
return result.ok
? { ok: true, value: jsonValueSchema.parse(result.value) }
: result;
},
});
interface JsonCommandOptions<Schema extends z.ZodType> {
readonly cli: CliInstance;
readonly logger: Logger;
readonly name: string;
readonly description: string;
readonly inputSchema: Schema;
readonly workflow: (
input: z.output<Schema>,
) =>
| { readonly ok: true; readonly value: JsonValue }
| { readonly ok: false; readonly error: AnalysisError };
}
const registerJsonCommand = <Schema extends z.ZodType>({
cli,
logger,
name,
description,
inputSchema,
workflow,
}: JsonCommandOptions<Schema>): void => {
cli.command(name, {
description,
args: z.object({
inputJson: z.string().describe("Inline workflow JSON or JSON file path"),
}),
run: ({ args }) =>
logCliCommand(logger, name, async () => {
const input = await parseCliJsonInput(args.inputJson, name);
if (!input.ok) return input.error;
const parsed = inputSchema.safeParse(input.value);
if (!parsed.success)
return {
error: "Application workflow failed",
...projectAnalysisError(
analysisInputErrorFromIssues(
name,
parsed.error.issues,
input.value,
),
),
};
const result = workflow(parsed.data);
return result.ok
? result.value
: {
error: "Application workflow failed",
...projectAnalysisError(result.error),
};
}),
});
};