diff --git a/.agents/skills/reverse-engineer-anything/references/native-and-artifacts.md b/.agents/skills/reverse-engineer-anything/references/native-and-artifacts.md index 65b68c6b2d..1a018534a2 100644 --- a/.agents/skills/reverse-engineer-anything/references/native-and-artifacts.md +++ b/.agents/skills/reverse-engineer-anything/references/native-and-artifacts.md @@ -37,8 +37,9 @@ unknown rather than false. For a completed `analyze_function` dossier, use `inspect_analysis_view` with `view: {kind: "native", facet: "value_flow_operations", offset: 0, limit: 32}` and the exact retained Evidence ID to read selected facts without reanalysis. -Native facets also include procedure, pseudocode, assembly, callers/callees, -references, and value-flow summaries. Pseudocode offsets and limits count UTF-16 +Native facets also include procedure, pseudocode, assembly, basic blocks, +comments, callers/callees, references, unresolved calls, referenced strings and +names, the native API record, and value-flow summaries. Pseudocode offsets and limits count UTF-16 code units; use the returned `next_offset` for continuation. Provider limitations and unavailable facts remain explicit; the complete parent Evidence is retained. diff --git a/docs/mcp-contracts.md b/docs/mcp-contracts.md index fd801cc0a1..b52ac6c331 100644 --- a/docs/mcp-contracts.md +++ b/docs/mcp-contracts.md @@ -262,8 +262,9 @@ Ordinary responses keep their existing complete result contract. `inspect_analysis_view` projects a caller-selected view of already completed `inspect_binary_layout`, `analyze_javascript_application`, or `analyze_function` -Evidence. Native views select procedure, pseudocode, assembly, callers/callees, -references, and high-pcode facets without starting a provider. Native offset +Evidence. Native views select procedure, pseudocode, assembly, basic blocks, +comments, callers/callees, references, unresolved calls, referenced strings and +names, the native API record, and high-pcode facets without starting a provider. Native offset and limit default to 0 and 64; pseudocode uses UTF-16 code units and never splits surrogate pairs, while other pages count rows. Unavailable facts and provider limitations remain explicit. Source is diff --git a/src/contracts/analysisViewToolContracts.ts b/src/contracts/analysisViewToolContracts.ts index be65b229f2..0edf10f604 100644 --- a/src/contracts/analysisViewToolContracts.ts +++ b/src/contracts/analysisViewToolContracts.ts @@ -15,7 +15,7 @@ export const ANALYSIS_VIEW_TOOL_CONTRACTS = [ ...toolContractMetadata("inspect_analysis_view"), kind: "application", description: - "Inspect selected views of completed binary-layout, JavaScript application or native analyze_function Evidence without repeating analysis. Use an exact same-session retained evidence_id or portable inline Evidence. Select a summary, layout mitigations/linkage facet, one section/symbol/module, or a stable page. Module pages include assets, bundled and source modules; use node_id for ambiguous or unavailable paths. Native views select procedure, pseudocode, assembly, callers/callees, references and high-pcode. Use nonnegative offset and a positive caller-selected limit (defaults 0 and 64); pseudocode uses UTF-16 code units, other native pages use rows. Returns artifact identity, parent Evidence ID, a distinct view digest, coverage, limitations and unknowns. Actual serialized size determines MCP transport admission; choose a smaller page or export the complete retained Evidence if needed.", + "Inspect selected views of completed binary-layout, JavaScript application or native analyze_function Evidence without repeating analysis. Use an exact same-session retained evidence_id or portable inline Evidence. Select a summary, layout mitigations/linkage facet, one section/symbol/module, or a stable page. Module pages include assets, bundled and source modules; use node_id for ambiguous or unavailable paths. Native views select procedure, pseudocode, assembly, basic blocks, comments, callers/callees, references, unresolved calls, referenced strings/names, native API and high-pcode. Use nonnegative offset and a positive caller-selected limit (defaults 0 and 64); pseudocode uses UTF-16 code units, other native pages use rows. Returns artifact identity, parent Evidence ID, a distinct view digest, coverage, limitations and unknowns. Actual serialized size determines MCP transport admission; choose a smaller page or export the complete retained Evidence if needed.", inputSchema: inspectAnalysisViewInputSchema, outputSchema: evidenceResultOf(analysisViewResultSchema), examples: [ diff --git a/src/domain/analysisView/analysisView.test.ts b/src/domain/analysisView/analysisView.test.ts index 1fd6fb7f1a..bc94baf4aa 100644 --- a/src/domain/analysisView/analysisView.test.ts +++ b/src/domain/analysisView/analysisView.test.ts @@ -161,6 +161,64 @@ it("preserves native API limitations and unavailable residual unknowns", () => { ); }); +it("pages every retained dossier collection and the native API record", () => { + const original = functionDossierSchema.parse(ghidraFunctionDossier()); + const page = ( + facet: + | "basic_blocks" + | "comments" + | "unresolved_calls" + | "referenced_strings" + | "referenced_names", + result: unknown = original, + ) => { + const view = projectAnalysisView(nativeParent(result), { + kind: "native", + facet, + offset: 0, + limit: 64, + }); + if (!view.ok) throw view.error; + return view.value; + }; + expect(page("basic_blocks")).toMatchObject({ + item: original.basic_blocks, + coverage: { total: original.basic_blocks.length, exhausted: true }, + }); + expect(page("referenced_strings")).toMatchObject({ + item: original.referenced_strings, + }); + expect( + page("comments", { + ...original, + comments: [{ address: "0x401000", kind: "comment", text: "entry" }], + }), + ).toMatchObject({ item: [{ text: "entry" }] }); + expect(page("referenced_names")).toMatchObject({ coverage: { total: 0 } }); + const { unresolved_calls: _omitted, ...withoutUnresolved } = original; + expect( + page("unresolved_calls", { + ...withoutUnresolved, + native_value_flow: { + available: false, + reason: "No high p-code", + limitations: [], + }, + }), + ).toMatchObject({ item: { available: false, reason: "not recorded" } }); + const api = projectAnalysisView(nativeParent(), { + kind: "native", + facet: "native_api", + offset: 0, + limit: 64, + }); + if (!api.ok) throw api.error; + expect(api.value).toMatchObject({ + item: original.native_api, + coverage: { status: "complete-within-view" }, + }); +}); + it("reports the malformed native dossier field", () => { const result = projectAnalysisView( nativeParent({ diff --git a/src/domain/analysisView/analysisView.ts b/src/domain/analysisView/analysisView.ts index db2d2dbac3..b31849b19c 100644 --- a/src/domain/analysisView/analysisView.ts +++ b/src/domain/analysisView/analysisView.ts @@ -82,10 +82,16 @@ const nativeViewSchema = z.strictObject({ "procedure", "pseudocode", "assembly", + "basic_blocks", + "comments", "callers", "callees", "incoming_references", "outgoing_references", + "unresolved_calls", + "referenced_strings", + "referenced_names", + "native_api", "value_flow_summary", "value_flow_operations", "value_flow_def_use", diff --git a/src/domain/analysisView/nativeFunctionView.ts b/src/domain/analysisView/nativeFunctionView.ts index d17f69b038..511453763b 100644 --- a/src/domain/analysisView/nativeFunctionView.ts +++ b/src/domain/analysisView/nativeFunctionView.ts @@ -104,17 +104,22 @@ const nativeRows = ( dossier: FunctionDossier, facet: Exclude< Extract["facet"], - "procedure" | "pseudocode" | "value_flow_summary" + "procedure" | "pseudocode" | "native_api" | "value_flow_summary" >, -) => { +): readonly unknown[] | undefined => { const sourceFlow = dossier.native_value_flow; const flow = sourceFlow?.available ? sourceFlow : undefined; return { assembly: dossier.assembly, + basic_blocks: dossier.basic_blocks, + comments: dossier.comments, callers: dossier.callers, callees: dossier.callees, incoming_references: dossier.incoming_references, outgoing_references: dossier.outgoing_references, + unresolved_calls: dossier.unresolved_calls, + referenced_strings: dossier.referenced_strings, + referenced_names: dossier.referenced_names, value_flow_operations: flow?.operations, value_flow_def_use: flow?.def_use, value_flow_effects: flow?.effects, @@ -152,10 +157,20 @@ export const projectNativeFunctionView = ( ...common, }); const flow = dossier.native_value_flow; - if (view.facet === "procedure" || view.facet === "value_flow_summary") { + if ( + view.facet === "procedure" || + view.facet === "native_api" || + view.facet === "value_flow_summary" + ) { if (view.offset !== 0) return err(viewError("Singleton native facets require offset 0.")); if (view.facet === "procedure") return ok(singleton(dossier.procedure)); + if (view.facet === "native_api") + return ok( + singleton( + dossier.native_api ?? { available: false, reason: "not recorded" }, + ), + ); const status = flow === null ? { available: false, reason: "not recorded" } @@ -188,7 +203,12 @@ export const projectNativeFunctionView = ( return ok( singleton({ available: false, - reason: flow?.available === false ? flow.reason : "not recorded", + // Only value-flow facets inherit the value-flow failure reason; an + // older dossier simply has no unresolved_calls record. + reason: + view.facet.startsWith("value_flow_") && flow?.available === false + ? flow.reason + : "not recorded", }), ); const items = rows.slice(view.offset, view.offset + view.limit);