refactor(javascript): consume normalized feature seeds

Require parsed seed facts in the pure trace projection and leave raw request defaults at the shared workflow boundary. Remove the redundant seed parse, unused raw-seed alias and duplicate seed-match binding.

Move omitted-versus-explicit defaults and full Evidence identity checks to compiled CLI/MCP acceptance. Reuse the owned MCP fixture and request files in the tests. Matching, native handoff, empty-literal, coverage and immutability checks remain covered; public schemas and successful outputs are unchanged.
This commit is contained in:
morluto committed 2026-10-23 10:22:07 +08:00
1 parent f4b2ba1313
commit 71a024c789
4 files changed
+96 -91

No files matched your search

@@ -293,34 +293,8 @@ describe("feature seed selection controls", () => {
);
});
describe("public feature trace from contextBridge source members", () => {
it.each(["openProject", "missingProject"])(
"normalizes optional seed defaults before deriving trace identity for %s",
(value) => {
const input = {
sourceEvidenceId,
graph: contextBridgeGraph(),
nativeEvidence: [],
direction: "both" as const,
};
const omitted = traceApplicationFeature({
...input,
seed: { kind: "api", value },
});
const explicit = traceApplicationFeature({
...input,
seed: {
kind: "api",
value,
match: null,
case_sensitive: false,
},
});
expect(omitted).toEqual(explicit);
},
);
it("traces one exact member seed through the public feature workflow", () => {
describe("feature trace projection from contextBridge source members", () => {
it("traces one exact member seed while preserving projection facts", () => {
const mode = {
value: "openProject",
match: "exact" as const,
@@ -12,8 +12,7 @@ import type {
} from "./javascriptApplicationGraphSchemas.js";
import {
applicationFeatureTraceResultSchema,
applicationFeatureSeedSchema,
type ApplicationFeatureSeedInput,
type ApplicationFeatureSeed,
type ApplicationFeatureTraceResult,
type TraceApplicationFeatureInput,
} from "./javascriptFeatureTraceSchemas.js";
@@ -32,23 +31,18 @@ export interface ApplicationFeatureTraceProjectionInput {
readonly sourceEvidenceId: string;
readonly graph: JavaScriptApplicationGraph;
readonly nativeEvidence: readonly Evidence[];
readonly seed: ApplicationFeatureSeedInput;
readonly seed: ApplicationFeatureSeed;
readonly direction: TraceApplicationFeatureInput["direction"];
}
/** Trace a literal feature through its complete reachable, authority-preserving JAG. */
export const traceApplicationFeature = (
source: ApplicationFeatureTraceProjectionInput,
input: ApplicationFeatureTraceProjectionInput,
): ApplicationFeatureTraceResult => {
const input = {
...source,
seed: applicationFeatureSeedSchema.parse(source.seed),
};
const allSeedMatches = findApplicationFeatureSeeds(
const seedMatches = findApplicationFeatureSeeds(
input.graph.nodes,
input.seed,
);
const seedMatches = allSeedMatches;
if (seedMatches.length === 0) return noMatchResult(input);
const traversal = traverseApplicationFeature(
input.graph,
@@ -86,7 +80,7 @@ export const traceApplicationFeature = (
coverage: {
status: traceCoverageStatus(input.graph),
source_graph_status: input.graph.coverage.status,
total_seed_matches: allSeedMatches.length,
total_seed_matches: seedMatches.length,
},
evidence_links: evidenceLinks,
limitations: traceLimitations(input.graph),
@@ -191,9 +191,6 @@ export type TraceApplicationFeatureInput = z.infer<
export type ApplicationFeatureSeed = z.output<
typeof applicationFeatureSeedSchema
>;
export type ApplicationFeatureSeedInput = z.input<
typeof applicationFeatureSeedSchema
>;
export type ApplicationFeatureTraceResult = z.infer<
typeof applicationFeatureTraceResultSchema
>;
@@ -1,7 +1,5 @@
import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import { writeFile } from "node:fs/promises";
import { join, resolve } from "node:path";
import { join } from "node:path";
import { expect } from "vitest";
import { z } from "zod";
@@ -12,7 +10,11 @@ import {
} from "../../../src/domain/javascript/javascriptFeatureTraceSchemas.js";
import { javascriptApplicationAnalysisResultSchema } from "../../../src/domain/javascript/javascriptApplicationAnalysis.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { cliTest, type TestCli } from "../../support/cli/cliFixture.js";
import {
cliTest,
type SharedMcpCall,
type TestCli,
} from "../../support/cli/cliFixture.js";
type FeatureSeed = z.input<typeof applicationFeatureSeedSchema>;
type Trace = z.infer<typeof applicationFeatureTraceResultSchema>;
@@ -155,44 +157,49 @@ const analyzeFixture = async (cli: TestCli, root: string) => {
const traceThroughCli = async (
cli: TestCli,
application: unknown,
scenario: SeedScenario,
): Promise<Trace> => {
scenario: Pick<SeedScenario, "name" | "seed">,
) => {
const root = await createTestTempDirectory("rea-feature-seed-request-");
const input = join(root, "trace.json");
await writeFile(
input,
JSON.stringify({ application, seed: scenario.seed, direction: "both" }),
);
const response = await cli.run({
arguments: [
"trace-application-feature",
JSON.stringify({
application,
seed: scenario.seed,
direction: "both",
}),
"--json",
],
arguments: ["trace-application-feature", input, "--json"],
});
expect(response.exitCode, `${scenario.name} via CLI`).toBe(0);
const evidence = toolContract("trace_application_feature").outputSchema.parse(
response.json,
);
return applicationFeatureTraceResultSchema.parse(evidence.normalized_result);
return {
evidence,
result: applicationFeatureTraceResultSchema.parse(
evidence.normalized_result,
),
};
};
const traceThroughMcp = async (
client: Client,
call: SharedMcpCall,
application: unknown,
scenario: SeedScenario,
): Promise<Trace> => {
const response = await client.callTool({
name: "trace_application_feature",
arguments: {
application,
seed: scenario.seed,
direction: "both",
},
scenario: Pick<SeedScenario, "name" | "seed">,
) => {
const response = await call("trace_application_feature", {
application,
seed: scenario.seed,
direction: "both",
});
expect(response.isError, `${scenario.name} via MCP`).not.toBe(true);
const evidence = toolContract("trace_application_feature").outputSchema.parse(
response.structuredContent,
);
return applicationFeatureTraceResultSchema.parse(evidence.normalized_result);
return {
evidence,
result: applicationFeatureTraceResultSchema.parse(
evidence.normalized_result,
),
};
};
const expectTrace = (trace: Trace, scenario: SeedScenario): void => {
@@ -229,28 +236,61 @@ cliTest(
`const notificationRoute = { path: "/accounts/preferences/notifications" };\nconst tinyRoute = { path: "/tiny" };\nmodule.exports = { notificationRoute, tinyRoute };\n`,
);
const { evidence, result } = await analyzeFixture(cli, root);
const client = new Client({ name: "feature-seed-selection", version: "1" });
const transport = new StdioClientTransport({
command: process.execPath,
args: [resolve("scripts/rea.mjs"), "mcp"],
cwd: process.cwd(),
env: { PATH: process.env.PATH ?? "", REA_LOG_LEVEL: "silent" },
stderr: "pipe",
});
transport.stderr?.on("data", () => undefined);
try {
await client.connect(transport);
for (const scenario of createSeedScenarios(result.graph)) {
const cliTrace = await traceThroughCli(cli, evidence, scenario);
const mcpTrace = await traceThroughMcp(client, evidence, scenario);
expectTrace(cliTrace, scenario);
expectTrace(mcpTrace, scenario);
expect(mcpTrace.seed_matches).toEqual(cliTrace.seed_matches);
expect(mcpTrace.coverage).toEqual(cliTrace.coverage);
}
} finally {
await client.close();
await transport.close();
const call = await cli.sharedMcpCall?.();
if (call === undefined) throw new Error("Expected file MCP session");
for (const scenario of createSeedScenarios(result.graph)) {
const { result: cliTrace } = await traceThroughCli(
cli,
evidence,
scenario,
);
const { result: mcpTrace } = await traceThroughMcp(
call,
evidence,
scenario,
);
expectTrace(cliTrace, scenario);
expectTrace(mcpTrace, scenario);
expect(mcpTrace.seed_matches).toEqual(cliTrace.seed_matches);
expect(mcpTrace.coverage).toEqual(cliTrace.coverage);
}
},
);
cliTest(
"normalizes API seed defaults before deriving matching and missing trace identities through CLI and MCP",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-feature-seed-defaults-");
await writeFile(
join(root, "preload.js"),
`
const { contextBridge } = require("electron");
contextBridge.exposeInMainWorld("desktop", { openProject: () => true });
`,
);
const { evidence } = await analyzeFixture(cli, root);
const call = await cli.sharedMcpCall?.();
if (call === undefined) throw new Error("Expected file MCP session");
for (const value of ["openProject", "missingProject"]) {
const omitted = { name: value, seed: { kind: "api" as const, value } };
const explicit = {
...omitted,
seed: { ...omitted.seed, match: null, case_sensitive: false },
};
const omittedCli = await traceThroughCli(cli, evidence, omitted);
const explicitCli = await traceThroughCli(cli, evidence, explicit);
const omittedMcp = await traceThroughMcp(call, evidence, omitted);
const explicitMcp = await traceThroughMcp(call, evidence, explicit);
expect(omittedCli, value).toEqual(explicitCli);
expect(omittedMcp, value).toEqual(explicitMcp);
expect(omittedMcp, value).toEqual(omittedCli);
expect(omittedCli.result.seed).toEqual(explicit.seed);
if (value === "openProject")
expect(omittedCli.result.summary.matched_seeds).toBeGreaterThan(0);
else {
expect(omittedCli.result.summary.matched_seeds).toBe(0);
expect(omittedCli.result.coverage.status).toBe("no-match");
}
}
},
);