fix(artifacts): describe incomplete Interface Builder graphs accurately
graph.truncated is also set for invalid archives, omitted dictionary keys and fixed decode budgets, not only caller-selected limits, so the view's unknown now points at coverage and limitations for the cause. The MCP test requests the summary first and reads its retained pages before any complete call, covering the flow where complete delivery cannot fit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
f993d33028
commit
da49ea2692
|
|
@ -252,7 +252,7 @@ Collections are `documents`, `nodes`, `edges`, and `coverage`; for nodes and
|
|||
edges an optional `document` keeps rows whose evidence names that exact decoded
|
||||
`relative_path`, and an undecoded path is refused. The summary keeps every
|
||||
decoded document, the provider, node-kind, edge-relation and coverage-status
|
||||
counts, and decoder truncation. Each page repeats `source_truncated`. A complete
|
||||
counts, and whether the decoded graph is incomplete. Each page repeats `source_truncated`. A complete
|
||||
app graph often exceeds the default MCP response budget (XLD.app: 6.7 MB), so
|
||||
select `detail: "summary"` on the first call. Delivery detail never reaches the
|
||||
decoder or changes the source Evidence ID.
|
||||
|
|
|
|||
|
|
@ -328,7 +328,7 @@ complete observation.
|
|||
"Contents/Resources/Base.lproj/MainMenu.nib", offset: 0, limit: 64}`.
|
||||
`document` selects rows whose evidence names that exact
|
||||
`documents[].relative_path` (nodes and edges only); `source_truncated` keeps
|
||||
decoder truncation visible on every page. `verify:interface-builder` compiles
|
||||
graph incompleteness visible on every page. `verify:interface-builder` compiles
|
||||
a source-owned AppKit XIB with `ibtool`. Storyboards require an installed
|
||||
iOS platform; unsupported archive forms remain explicit.
|
||||
XML plist archives accept UTF-8 and BOM-marked UTF-16 in either byte order.
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ describe("Interface Builder analysis views", () => {
|
|||
items: report().graph.coverage,
|
||||
source_truncated: true,
|
||||
unknowns: expect.arrayContaining([
|
||||
expect.stringContaining("stopped at a caller-selected bound"),
|
||||
expect.stringContaining("The decoded graph is incomplete"),
|
||||
]),
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -188,7 +188,7 @@ export const projectInterfaceBuilderView = (
|
|||
"Rows describe recognized archive objects and connections only; unrecognized classes, fields and runtime-created UI remain unknown.",
|
||||
...(graph.truncated
|
||||
? [
|
||||
"The decoder stopped at a caller-selected bound; page exhaustion does not establish absence of undecoded objects or connections.",
|
||||
"The decoded graph is incomplete (see coverage and limitations for the cause); page exhaustion does not establish absence of undecoded objects or connections.",
|
||||
]
|
||||
: []),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -67,18 +67,7 @@ mcpTest(
|
|||
const app = await fixtureApp();
|
||||
const client = await openProductionTarget(mcp, onTestFinished, app);
|
||||
|
||||
const complete = await client.callTool({
|
||||
name: "decode_interface_builder",
|
||||
arguments: {},
|
||||
});
|
||||
expect(complete.isError, JSON.stringify(complete.content)).not.toBe(true);
|
||||
const full = evidenceOf(interfaceBuilderAnalysisSchema).parse(
|
||||
complete.structuredContent,
|
||||
);
|
||||
expect(full.parameters).not.toHaveProperty("detail");
|
||||
const report = full.normalized_result;
|
||||
expect(report.documents).toHaveLength(2);
|
||||
|
||||
// Summary first: its own retained parent must serve every later page.
|
||||
const summarized = await client.callTool({
|
||||
name: "decode_interface_builder",
|
||||
arguments: { detail: "summary" },
|
||||
|
|
@ -89,15 +78,6 @@ mcpTest(
|
|||
const summary = evidenceOf(summarySchema).parse(
|
||||
summarized.structuredContent,
|
||||
).normalized_result;
|
||||
// Delivery detail never reaches the decoder or the source Evidence ID.
|
||||
expect(summary.parent_evidence_id).toBe(full.evidence_id);
|
||||
expect(summary.summary.documents).toEqual(report.documents);
|
||||
expect(summary.summary.available_counts).toEqual({
|
||||
nodes: report.graph.nodes.length,
|
||||
edges: report.graph.edges.length,
|
||||
coverage: report.graph.coverage.length,
|
||||
});
|
||||
|
||||
const source = {
|
||||
kind: "retained-evidence",
|
||||
evidence_id: summary.parent_evidence_id,
|
||||
|
|
@ -122,16 +102,37 @@ mcpTest(
|
|||
}
|
||||
return rows;
|
||||
};
|
||||
expect(await readAll({ collection: "edges" })).toEqual(report.graph.edges);
|
||||
const main = "Contents/Resources/Main.nib";
|
||||
const mainNodes = report.graph.nodes.filter(({ evidence }) =>
|
||||
const edges = await readAll({ collection: "edges" });
|
||||
const mainNodes = await readAll({ collection: "nodes", document: main });
|
||||
|
||||
// Only then compare with a complete call made for reference.
|
||||
const complete = await client.callTool({
|
||||
name: "decode_interface_builder",
|
||||
arguments: {},
|
||||
});
|
||||
expect(complete.isError, JSON.stringify(complete.content)).not.toBe(true);
|
||||
const full = evidenceOf(interfaceBuilderAnalysisSchema).parse(
|
||||
complete.structuredContent,
|
||||
);
|
||||
expect(full.parameters).not.toHaveProperty("detail");
|
||||
const report = full.normalized_result;
|
||||
expect(report.documents).toHaveLength(2);
|
||||
// Delivery detail never reaches the decoder or the source Evidence ID.
|
||||
expect(summary.parent_evidence_id).toBe(full.evidence_id);
|
||||
expect(summary.summary.documents).toEqual(report.documents);
|
||||
expect(summary.summary.available_counts).toEqual({
|
||||
nodes: report.graph.nodes.length,
|
||||
edges: report.graph.edges.length,
|
||||
coverage: report.graph.coverage.length,
|
||||
});
|
||||
expect(edges).toEqual(report.graph.edges);
|
||||
const expectedMainNodes = report.graph.nodes.filter(({ evidence }) =>
|
||||
evidence.some(({ artifact_path }) => artifact_path === main),
|
||||
);
|
||||
expect(mainNodes.length).toBeGreaterThan(0);
|
||||
expect(mainNodes.length).toBeLessThan(report.graph.nodes.length);
|
||||
expect(await readAll({ collection: "nodes", document: main })).toEqual(
|
||||
mainNodes,
|
||||
);
|
||||
expect(expectedMainNodes.length).toBeGreaterThan(0);
|
||||
expect(expectedMainNodes.length).toBeLessThan(report.graph.nodes.length);
|
||||
expect(mainNodes).toEqual(expectedMainNodes);
|
||||
|
||||
const unknownDocument = await client.callTool({
|
||||
name: "inspect_analysis_view",
|
||||
|
|
|
|||
Loading…
Reference in New Issue