feat(javascript): analyze selected application sources

Analyze exact selected sources in first-request order before unrelated
static scopes, preserving full artifact identity and byte verification.
Retain selection in CLI/MCP Evidence and summaries, with explicit unknowns
and partial coverage for unselected sources. Keep default full analysis.

Fixes #1848

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
This commit is contained in:
Ysopwnable
2026-10-12 04:07:14 +08:00
committed by GitHub
co-authored by factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> Kaoru0822
parent 0d8b728d24
commit 85c1d2587a
24 changed files with 790 additions and 18 deletions
@@ -3,6 +3,9 @@ import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { z } from "zod";
import { parseEvidence } from "../../domain/evidence.js";
import { javascriptApplicationAnalysisResultSchema } from "../../domain/javascript/javascriptApplicationAnalysis.js";
import { writeJavaScriptSourceSelectionFixture } from "../../../tests/fixtures/javascriptSourceSelection.js";
import { ArtifactReaderFailure } from "../../artifacts/ArtifactReader.js";
import { scanCanonicalArtifactInventory } from "../../../tests/fixtures/artifactInventory.js";
@@ -10,6 +13,110 @@ import { createTestTempDirectory } from "../../../tests/fixtures/temporaryDirect
import { projectAnalysisError } from "../../domain/analysisErrorProjection.js";
import { analyzeJavaScriptApplication } from "../../../tests/support/javascriptApplicationScope.js";
describe("Selected JavaScript source failure diagnostics", () => {
it("analyzes selected files in first-request order and reports only selected progress", async () => {
const root = await createTestTempDirectory("rea-js-selected-priority-");
await writeJavaScriptSourceSelectionFixture(root);
await writeFile(join(root, "z-later.mjs"), "export const later = 1;\n");
const progress: string[] = [];
const result = await analyzeJavaScriptApplication(
{
input_path: root,
source_paths: ["z-later.mjs", "main.mjs", "z-later.mjs"],
},
{
progress: {
report: async (event) => {
if (event.phase === "parse_javascript_source")
progress.push(event.message);
},
},
},
);
expect(result.ok).toBe(true);
expect(progress).toEqual([
"Parsing and projecting z-later.mjs (1/2)",
"Parsing and projecting main.mjs (2/2)",
]);
});
it("verifies unselected bytes against the inventoried identity without decoding them", async () => {
const root = await createTestTempDirectory("rea-js-unselected-integrity-");
await writeJavaScriptSourceSelectionFixture(root);
const result = await analyzeJavaScriptApplication(
{ input_path: root, source_paths: ["main.mjs"] },
{
progress: {
report: async (event) => {
if (event.phase === "read_javascript_artifacts")
await writeFile(
join(root, "vendor.js"),
"changed after inventory\n",
);
},
},
},
);
if (result.ok)
throw new Error(
"Unselected content changes must still fail integrity verification",
);
expect(projectAnalysisError(result.error)).toMatchObject({
code: "artifact_operation_failed",
details: { reason: "integrity" },
});
});
it("retains selected facts and unselected scope after cancellation between selected sources", async () => {
const root = await createTestTempDirectory("rea-js-selected-cancellation-");
await writeJavaScriptSourceSelectionFixture(root);
await writeFile(join(root, "z-later.mjs"), "export const later = 1;\n");
const controller = new AbortController();
const source_paths = ["main.mjs", "z-later.mjs"];
const result = await analyzeJavaScriptApplication(
{ input_path: root, source_paths },
{
signal: controller.signal,
progress: {
report: async (event) => {
if (
event.phase === "parse_javascript_source" &&
event.message.includes("z-later.mjs")
)
controller.abort();
},
},
},
);
if (result.ok) throw new Error("Expected selected-source cancellation");
expect(projectAnalysisError(result.error).code).toBe("cancelled");
const evidence = parseEvidence(result.error.partialObservation);
const partial = javascriptApplicationAnalysisResultSchema.parse(
evidence.normalized_result,
);
expect(partial.source_selection).toEqual({
requested_paths: source_paths,
unselected_files: 5,
});
expect(
partial.semantic_graph.nodes.some(
({ kind, label }) => kind === "function" && label === "selected",
),
).toBe(true);
expect(partial.statistics.parsed_javascript_files).toBe(1);
expect(partial.graph.coverage.status).toBe("partial");
expect(
partial.graph.nodes
.flatMap(({ observations }) =>
observations.map(({ properties }) => properties),
)
.filter(
(properties) => properties.text_status === "outside-analysis-scope",
),
).toHaveLength(5);
});
});
describe("JavaScript application failure diagnostics", () => {
it("identifies the rejected result field in caller-visible diagnostics", async () => {
const inputPath = await createTestTempDirectory("rea-js-schema-failure-");