mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
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:
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");
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
Reference in new issue
Block a user