Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions __tests__/external/insight-provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ExternalInsightSchema } from "agent-contracts-analyzer";
import {
MicroContractsInsightProvider,
buildExternalInsight,
filterInsight,
} from "../../src/external/insight-provider.js";

const EXAMPLES_ROOT = path.resolve(import.meta.dirname, "../../examples");
Expand Down Expand Up @@ -85,6 +86,61 @@ describe("MicroContractsInsightProvider", () => {
"packages/contract/billing/services/BillingServiceApi.ts#createInvoice",
);
});

it("filters edges by changedFiles to those touching the matching module", async () => {
const provider = new MicroContractsInsightProvider();
const filtered = await provider.provide({
projectRoot: EXAMPLES_ROOT,
changedFiles: ["spec/billing/openapi/billing.yaml"],
});

expect(filtered.edges.length).toBeGreaterThan(0);
expect(
filtered.edges.some(
(e) =>
e.from === "billing" &&
e.to === "core.User.getUsers" &&
e.kind === "api_dependency",
),
).toBe(true);
for (const edge of filtered.edges) {
const touchesBillingModule =
edge.from === "billing" || edge.from.startsWith("billing.");
const touchesBillingDependency =
edge.to === "billing" || edge.to.startsWith("billing.");
expect(touchesBillingModule || touchesBillingDependency).toBe(true);
}
expect(filtered.anchorMapping?.some((a) => a.domainId === "billing")).toBe(
true,
);
});

it("filters edges by artifactIds", async () => {
const provider = new MicroContractsInsightProvider();
const fullInsight = buildExternalInsight(EXAMPLES_ROOT);
const filtered = await provider.provide({
projectRoot: EXAMPLES_ROOT,
artifactIds: ["billing.Billing.createInvoice"],
});

expect(filtered.edges.length).toBeLessThan(fullInsight.edges.length);
expect(filtered.edges).toHaveLength(1);
expect(filtered.edges[0]).toMatchObject({
from: "billing.Billing.createInvoice",
to: "core.User.getUserById",
kind: "api_operation_dependency",
});
expect(filtered.anchorMapping?.map((a) => a.domainId).sort()).toEqual(
["billing.Billing.createInvoice", "core.User.getUserById"].sort(),
);
});

it("returns all edges when no filters are specified", () => {
const insight = buildExternalInsight(EXAMPLES_ROOT);
const unfiltered = filterInsight(insight, { projectRoot: EXAMPLES_ROOT });
expect(unfiltered.edges).toEqual(insight.edges);
expect(unfiltered.anchorMapping).toEqual(insight.anchorMapping);
});
});

describe("buildExternalInsight error handling", () => {
Expand Down
6 changes: 3 additions & 3 deletions examples/packages/.generated-manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"version": "1.0",
"generatorVersion": "0.17.7",
"generatorVersion": "0.17.8",
"files": {
"contract-published/billing/docs/api-reference.html": {
"sha256": "5b69e88fc9f60578261a8231132678550a40a70eb52dfcc611f48937f448ca44"
Expand Down Expand Up @@ -144,6 +144,6 @@
"sha256": "d791f51b36215aba3e7cf7c5f69fc4b9fd6d4f2cf515718fdc93589fa05e2608"
}
},
"inputHash": "338fbc40d41809be96cfe07d8b97e9e7e652387d2bb290593310df6d3f3345d8",
"updatedAt": "2026-06-06T16:42:53.184Z"
"inputHash": "9bcb8bb05aacf77b0826790904e5bedbf889468a39bb1369af7d340c587e5041",
"updatedAt": "2026-06-06T16:59:45.158Z"
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "micro-contracts",
"version": "0.17.7",
"version": "0.17.8",
"description": "Contract-first OpenAPI toolchain that keeps TypeScript UI and microservices aligned via code generation",
"type": "module",
"main": "dist/index.js",
Expand Down
48 changes: 47 additions & 1 deletion src/external/insight-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,11 +350,57 @@ export function buildExternalInsight(
};
}

export function filterInsight(
insight: ExternalInsight,
query: InsightQuery,
): ExternalInsight {
const { changedFiles, artifactIds } = query;
if (!changedFiles?.length && !artifactIds?.length) return insight;

const relevantIds = new Set<string>();

if (changedFiles?.length) {
const changedSet = new Set(changedFiles);
for (const anchor of insight.anchorMapping ?? []) {
if (anchor.filePaths.some((fp) => changedSet.has(fp))) {
relevantIds.add(anchor.domainId);
}
}
}

if (artifactIds?.length) {
for (const id of artifactIds) {
relevantIds.add(id);
}
}

const filteredEdges = insight.edges.filter(
(e) => relevantIds.has(e.from) || relevantIds.has(e.to),
);

const referencedIds = new Set<string>();
for (const edge of filteredEdges) {
referencedIds.add(edge.from);
referencedIds.add(edge.to);
}
const filteredAnchors = (insight.anchorMapping ?? []).filter((a) =>
referencedIds.has(a.domainId),
);

return {
...insight,
edges: filteredEdges,
anchorMapping:
filteredAnchors.length > 0 ? filteredAnchors : undefined,
};
}

export class MicroContractsInsightProvider implements InsightProvider {
readonly name = MICRO_CONTRACTS_INSIGHT_SOURCE;

async provide(query: InsightQuery): Promise<ExternalInsight> {
return buildExternalInsight(query.projectRoot);
const insight = buildExternalInsight(query.projectRoot);
return filterInsight(insight, query);
}
}

Expand Down
Loading