Files
reverse-engineering-anything/src/cli/javascriptApplicationAnalysis.ts
T
gensokyo-devandgensokyo-dev b990eb496d perf(cli): load only the registrar a one-shot command needs
Every rea process imported and registered all 52 command registrars
(about 1,900 modules) before running any command. A one-shot command
now loads only the registrar that defines its top-level command.

- src/cli/cliCommandRegistrations.ts holds every registrar in one table,
  grouped by tool family, with its module loader and its call. The
  complete createCli registers the table with static imports, and the
  types check that each entry matches its module.
- src/cli/cliShell.ts owns what does not depend on which commands are
  registered: identity, globals, argument policy, completions and the
  registration builtins. The full and lazy paths share it.
- scripts/record-cli-commands.mjs records, after tsc, which registrar
  defines each top-level command, from the same compiled table. The
  bundle keeps the record as an external dist module.
- cli/cliLoader.ts selects the recorded registrar when a known top-level
  command runs. It loads the complete CLI for help, --schema, --llms,
  completions, builtins, unknown names ("did you mean"), or a missing
  record.
- Process-owned CLI resources register their closers when their module
  loads (cli/cliResources.ts), so the shell no longer imports
  JavaScript-analysis and guidance owners just to close them.

Measured on one pinned core (median of 5 warm runs, base main vs this
change):
- guidance: 1.95 s / 253 MB → 0.42 s / 101 MB
- list-cutter-sessions: 1.97 s → 0.49 s
- inspect-go-binary: 1.95 s → 0.95 s
- setup --dry-run: 2.40 s → 1.88 s
- analyze: 2.39 s → 1.78 s
- --help: 1.99 s → 2.04 s (complete CLI, as before)

Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
2026-11-14 20:25:11 +08:00

180 lines
6.5 KiB
TypeScript

import type { AnalysisViewRequest } from "../domain/analysisView/analysisViewRequest.js";
import { publishCliAnalysisEvidence } from "./analysisEvidenceDelivery.js";
import { JAVASCRIPT_APPLICATION_PROVIDER } from "../domain/investigationProviders.js";
import { createProgressReporter } from "../application/ProgressReporter.js";
import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js";
import { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js";
import { analysisErrorWithCleanupFailure } from "../domain/analysisErrorCleanup.js";
import { completeProviderEvidence } from "../application/ProviderOperationExecution.js";
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
import type { JsonValue } from "../domain/jsonValue.js";
import { ProviderCleanupError } from "../domain/providerCleanupError.js";
import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js";
import { javaScriptSourcePathsSchema } from "../domain/javascript/javascriptSourceSelection.js";
import { parseCliJsonInput } from "../cliJsonInput.js";
import { ownCliProcessResource } from "./cliResources.js";
const OPERATION = "analyze_javascript_application";
/**
* Combine repeated `--source-path` values with `--source-paths-json`, an inline
* JSON array or a JSON file. A file keeps large selections below the host
* command-line limit; entries remain exact application-relative paths and are
* not resolved against the working directory.
*/
export const cliJavaScriptSourcePaths = async (
sourcePath: readonly string[] | undefined,
sourcePathsJson: string | undefined,
signal?: AbortSignal,
): Promise<
| { readonly ok: true; readonly value: readonly string[] | undefined }
| { readonly ok: false; readonly error: JsonValue }
> => {
if (sourcePathsJson === undefined) return { ok: true, value: sourcePath };
const input = await parseCliJsonInput(sourcePathsJson, OPERATION, signal);
if (!input.ok) return input;
const parsed = javaScriptSourcePathsSchema.safeParse(input.value);
if (!parsed.success)
return {
ok: false,
error: cliError(
analysisInputErrorFromIssues(
OPERATION,
parsed.error.issues.map((issue) => ({
...issue,
path: ["source_paths_json", ...issue.path],
})),
{ source_paths_json: input.value },
),
),
};
return { ok: true, value: [...(sourcePath ?? []), ...parsed.data] };
};
/**
* One parked JavaScript worker for one CLI host. A healthy worker stays
* parked between that host's commands. Independent hosts do not share it.
*/
export class CliJavaScriptAnalysisHost {
#retainedScope: ArtifactResourceScope | undefined;
/** One shutdown shared by the exit callback and the dispatch `finally`. */
#closing: Promise<void> | undefined;
scope(): ArtifactResourceScope {
this.#retainedScope ??= new ArtifactResourceScope();
return this.#retainedScope;
}
/**
* Stop this host's worker. Overlapping callers share the attempt in flight.
* The scope stays owned when shutdown fails, so the next call retries.
*/
close(): Promise<void> {
if (this.#closing !== undefined) return this.#closing;
const scope = this.#retainedScope;
if (scope === undefined) return Promise.resolve();
let published: Promise<void>;
published = scope
.close()
.then(() => {
if (this.#retainedScope === scope) this.#retainedScope = undefined;
})
.finally(() => {
if (this.#closing === published) this.#closing = undefined;
});
this.#closing = published;
return published;
}
}
/** Process CLI host used when a caller does not pass its own host. */
const processJavaScriptAnalysis = new CliJavaScriptAnalysisHost();
/** Stop the process-owned JavaScript worker. */
export const closeCliJavaScriptResources = (
host: CliJavaScriptAnalysisHost = processJavaScriptAnalysis,
): Promise<void> => host.close();
ownCliProcessResource("javascript-analysis", () =>
closeCliJavaScriptResources(),
);
/** Execute the shared CLI boundary for static JavaScript analysis. */
export const runCliJavaScriptApplicationAnalysis = async (
input: unknown,
environment: Readonly<Record<string, string | undefined>>,
signal?: AbortSignal,
delivery?: {
readonly detail: "complete" | "summary";
readonly evidenceOutput: string | undefined;
readonly view?: AnalysisViewRequest;
},
host: CliJavaScriptAnalysisHost = processJavaScriptAnalysis,
): Promise<JsonValue> => {
const { analyzeJavaScriptApplication } =
await import("../application/javascript/JavaScriptApplicationService.js");
const progress = createProgressReporter(
async (update) => {
process.stderr.write(`${JSON.stringify({ rea_progress: update })}\n`);
},
{ minimumIntervalMs: 0 },
);
const resources = host.scope();
const result = await analyzeJavaScriptApplication(input, resources, {
environment,
progress,
...(signal === undefined ? {} : { signal }),
});
try {
// Retry failed owners without stopping a healthy parked worker.
await resources.retryCleanup();
} catch (cause: unknown) {
const cleanup = ArtifactReaderFailure.cleanupObservation(
cause,
"JavaScript application resources",
);
const failure = new ProviderCleanupError(
JAVASCRIPT_APPLICATION_PROVIDER.id,
cleanup.resources,
{ reason: cleanup.reason },
{ cause, operation: "analyze_javascript_application" },
);
const completed = result.ok
? completeProviderEvidence(result.value, signal)
: result;
if (completed.ok) failure.retainPartialObservation(completed.value);
return cliError(
completed.ok
? failure
: analysisErrorWithCleanupFailure(
completed.error,
failure,
"analyze_javascript_application",
),
);
}
const completed = result.ok
? completeProviderEvidence(result.value, signal)
: result;
const finalized = completed.ok
? completed
: { ok: false as const, error: cliError(completed.error) };
return delivery === undefined
? finalized.ok
? finalized.value
: finalized.error
: publishCliAnalysisEvidence(finalized, {
...delivery,
providerId: JAVASCRIPT_APPLICATION_PROVIDER.id,
operation: "analyze_javascript_application",
signal: signal ?? new AbortController().signal,
});
};
const cliError = (
error: Parameters<typeof projectAnalysisError>[0],
): JsonValue => ({
error: "JavaScript application analysis failed",
...projectAnalysisError(error),
});