From fd5c5467e0a7900ba81b4e73033ef8fa43073736 Mon Sep 17 00:00:00 2001 From: Ruben Cuevas Date: Sat, 9 May 2026 19:17:35 -0400 Subject: [PATCH] fix(exec-approvals): lazy-load command explainer --- .../command-analysis/explain.lazy.test.ts | 24 +++++++++++++++++++ src/infra/command-analysis/explain.test.ts | 12 ++++++++++ src/infra/command-analysis/explain.ts | 2 +- 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 src/infra/command-analysis/explain.lazy.test.ts diff --git a/src/infra/command-analysis/explain.lazy.test.ts b/src/infra/command-analysis/explain.lazy.test.ts new file mode 100644 index 00000000000..e5ac2c5b13c --- /dev/null +++ b/src/infra/command-analysis/explain.lazy.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, it, vi } from "vitest"; + +vi.mock("../command-explainer/extract.js", () => { + throw new Error("command explainer should not load for lightweight summaries"); +}); + +describe("command-analysis lazy command explainer", () => { + it("does not load tree-sitter parser dependencies for policy summaries", async () => { + const { resolveCommandAnalysisSummaryForDisplay } = await import("./explain.js"); + + expect( + resolveCommandAnalysisSummaryForDisplay({ + host: "gateway", + commandText: "python3 -c 'print(1)'", + }), + ).toEqual( + expect.objectContaining({ + commandCount: 1, + riskKinds: ["inline-eval"], + warningLines: ["Contains inline-eval: python3 -c"], + }), + ); + }); +}); diff --git a/src/infra/command-analysis/explain.test.ts b/src/infra/command-analysis/explain.test.ts index 161d8dabe70..b43658240a1 100644 --- a/src/infra/command-analysis/explain.test.ts +++ b/src/infra/command-analysis/explain.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from "vitest"; import { explainShellCommand } from "../command-explainer/index.js"; import { + explainCommandForDisplay, resolveCommandAnalysisSummaryForDisplay, summarizeCommandExplanation, summarizeCommandSegmentsForDisplay, @@ -19,6 +20,17 @@ describe("command-analysis explanation summary", () => { ); }); + it("loads the rich command explainer for rich display summaries", async () => { + const result = await explainCommandForDisplay(`bash -lc 'python3 -c "print(1)"'`); + + expect(result?.summary).toEqual( + expect.objectContaining({ + commandCount: 1, + riskKinds: expect.arrayContaining(["shell-wrapper", "inline-eval"]), + }), + ); + }); + it("summarizes policy command segments without async parsing", () => { const summary = summarizeCommandSegmentsForDisplay([ { diff --git a/src/infra/command-analysis/explain.ts b/src/infra/command-analysis/explain.ts index 0fbbed43e2c..ef0da9c82bf 100644 --- a/src/infra/command-analysis/explain.ts +++ b/src/infra/command-analysis/explain.ts @@ -1,4 +1,3 @@ -import { explainShellCommand } from "../command-explainer/extract.js"; import type { CommandExplanation, CommandRisk } from "../command-explainer/types.js"; import type { ExecCommandSegment } from "../exec-approvals-analysis.js"; import { analyzeCommandForPolicy } from "./policy.js"; @@ -122,6 +121,7 @@ export async function explainCommandForDisplay( command: string, ): Promise<{ explanation: CommandExplanation; summary: CommandExplanationSummary } | null> { try { + const { explainShellCommand } = await import("../command-explainer/extract.js"); const explanation = await explainShellCommand(command); return { explanation, summary: summarizeCommandExplanation(explanation) }; } catch {