rea-agents 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/README.md +124 -20
  2. package/bridge/hopper_bridge.py +141 -13
  3. package/dist/application/AnalysisProvider.js +12 -1
  4. package/dist/application/ArtifactExtraction.js +166 -0
  5. package/dist/application/ArtifactGraphConstruction.js +257 -0
  6. package/dist/application/ArtifactInventory.js +253 -0
  7. package/dist/application/BinarySession.js +164 -14
  8. package/dist/application/CompositeProvider.js +73 -0
  9. package/dist/application/DirectAnalysis.js +27 -3
  10. package/dist/application/Doctor.js +35 -6
  11. package/dist/application/EnhancedTools.js +8 -7
  12. package/dist/application/EvidenceBundleCommands.js +29 -0
  13. package/dist/application/EvidenceBundleFiles.js +127 -0
  14. package/dist/application/EvidenceLedger.js +225 -18
  15. package/dist/application/FilesystemSnapshot.js +124 -0
  16. package/dist/application/LinuxHopper.js +186 -0
  17. package/dist/application/LoopbackReplay.js +195 -37
  18. package/dist/application/ProcessHarness.js +200 -191
  19. package/dist/application/ProcessNormalization.js +44 -0
  20. package/dist/application/ProcessOwnership.js +84 -0
  21. package/dist/application/ProcessSampling.js +284 -0
  22. package/dist/application/RealHopperAssertions.js +105 -0
  23. package/dist/application/ReferenceSourceImport.js +182 -0
  24. package/dist/application/ReferenceSourceImportEntries.js +122 -0
  25. package/dist/application/ReferenceSourceImportPolicy.js +73 -0
  26. package/dist/application/ReferenceSourceImportTypes.js +18 -0
  27. package/dist/application/ReferenceSourceVcsAdapter.js +34 -0
  28. package/dist/application/Setup.js +192 -41
  29. package/dist/application/Uninstall.js +130 -0
  30. package/dist/application/runtime.js +8 -1
  31. package/dist/artifacts/ArtifactPaths.js +51 -0
  32. package/dist/artifacts/ArtifactProvider.js +130 -0
  33. package/dist/artifacts/ArtifactReader.js +9 -0
  34. package/dist/artifacts/AsarArtifactReader.js +62 -0
  35. package/dist/artifacts/DirectoryArtifactReader.js +107 -0
  36. package/dist/artifacts/MachOSliceArtifactReader.js +66 -0
  37. package/dist/artifacts/SafeOutputTree.js +199 -0
  38. package/dist/artifacts/StreamBytes.js +10 -0
  39. package/dist/artifacts/ZipArtifactReader.js +109 -0
  40. package/dist/cli.js +149 -2
  41. package/dist/config.js +35 -1
  42. package/dist/contracts/artifactComparisonExample.js +95 -0
  43. package/dist/contracts/artifactToolContracts.js +84 -0
  44. package/dist/contracts/enhancedInputs.js +4 -0
  45. package/dist/contracts/functionComparisonExample.js +57 -0
  46. package/dist/contracts/investigationExamples.js +118 -0
  47. package/dist/contracts/nativeToolContracts.js +52 -0
  48. package/dist/contracts/processCaptureExample.js +25 -0
  49. package/dist/contracts/toolContractExamples.js +69 -0
  50. package/dist/contracts/toolContracts.js +99 -36
  51. package/dist/contracts/toolOutputSchemas.js +154 -82
  52. package/dist/contracts/unknownContractExamples.js +33 -0
  53. package/dist/domain/artifactComparison.js +273 -0
  54. package/dist/domain/artifactGraph.js +194 -0
  55. package/dist/domain/artifactInventoryEvidence.js +150 -0
  56. package/dist/domain/binaryTarget.js +55 -0
  57. package/dist/domain/bundleComparison.js +266 -0
  58. package/dist/domain/callPath.js +346 -0
  59. package/dist/domain/changedBehavior.js +294 -0
  60. package/dist/domain/errors.js +192 -4
  61. package/dist/domain/evidence.js +41 -10
  62. package/dist/domain/evidenceBundle.js +187 -6
  63. package/dist/domain/functionComparison.js +201 -0
  64. package/dist/domain/functionComparisonNormalization.js +112 -0
  65. package/dist/domain/functionComparisonResults.js +54 -0
  66. package/dist/domain/functionComparisonSchemas.js +82 -0
  67. package/dist/domain/functionDossierEvidence.js +171 -0
  68. package/dist/domain/hopperValues.js +14 -7
  69. package/dist/domain/nativeInspection.js +142 -0
  70. package/dist/domain/processCapture.js +152 -54
  71. package/dist/domain/processComparison.js +106 -0
  72. package/dist/domain/reconstructionUnknowns.js +90 -0
  73. package/dist/domain/reconstructionVerification.js +285 -0
  74. package/dist/domain/reconstructionVerificationSchemas.js +126 -0
  75. package/dist/domain/referenceSourceClassification.js +496 -0
  76. package/dist/domain/referenceSourceGraph.js +376 -0
  77. package/dist/domain/referenceSourceImportParsing.js +235 -0
  78. package/dist/domain/referenceSourcePolicy.js +1 -0
  79. package/dist/domain/residualUnknown.js +239 -0
  80. package/dist/domain/staticRuntimeCorrelation.js +375 -0
  81. package/dist/hopper/BridgeLauncher.js +40 -3
  82. package/dist/hopper/HopperClient.js +14 -5
  83. package/dist/hopper/HopperProvider.js +57 -22
  84. package/dist/identity.js +1 -0
  85. package/dist/main.js +5 -1
  86. package/dist/native/CommandRunner.js +156 -0
  87. package/dist/native/NativeMacOSProvider.js +306 -0
  88. package/dist/native/NativeMachoInspection.js +135 -0
  89. package/dist/native/parsers/codesign.js +55 -0
  90. package/dist/native/parsers/demangle.js +26 -0
  91. package/dist/native/parsers/dyldInfo.js +25 -0
  92. package/dist/native/parsers/lipo.js +67 -0
  93. package/dist/native/parsers/otool.js +193 -0
  94. package/dist/native/parsers/plist.js +23 -0
  95. package/dist/reference/ReferenceSourceReader.js +73 -0
  96. package/dist/reference/ReferenceSourceReaderEntries.js +206 -0
  97. package/dist/reference/ReferenceSourceReaderErrors.js +19 -0
  98. package/dist/reference/ReferenceSourceReaderFile.js +119 -0
  99. package/dist/reference/ReferenceSourceReaderPaths.js +23 -0
  100. package/dist/reference/ReferenceSourceReaderTypes.js +2 -0
  101. package/dist/reference/ReferenceSourceReaderValidate.js +71 -0
  102. package/dist/server/createServer.js +31 -5
  103. package/dist/server/recordDerivedEvidence.js +10 -0
  104. package/dist/server/registerArtifactComparisonTool.js +62 -0
  105. package/dist/server/registerArtifactTools.js +6 -0
  106. package/dist/server/registerBundleComparisonTool.js +47 -0
  107. package/dist/server/registerEnhancedTools.js +64 -12
  108. package/dist/server/registerEvidenceTools.js +36 -0
  109. package/dist/server/registerFunctionComparisonTool.js +68 -0
  110. package/dist/server/registerInvestigationTools.js +224 -0
  111. package/dist/server/registerNativeTools.js +6 -0
  112. package/dist/server/registerOfficialTools.js +47 -14
  113. package/dist/server/registerProcessComparisonTool.js +106 -0
  114. package/dist/server/registerSessionTools.js +179 -70
  115. package/dist/server/sessionEvidence.js +28 -0
  116. package/dist/server/sessionToolPolicies.js +64 -0
  117. package/dist/server/toolRegistrationOptions.js +7 -0
  118. package/dist/server/toolResult.js +8 -5
  119. package/install.sh +198 -0
  120. package/package.json +18 -1
  121. package/scripts/rea.mjs +5 -1
  122. package/skills/rea-analysis/SKILL.md +77 -2
@@ -0,0 +1,118 @@
1
+ import { compareFunctions } from "../domain/functionComparison.js";
2
+ import { createEvidence } from "../domain/evidence.js";
3
+ import { jsonValueSchema } from "../domain/jsonValue.js";
4
+ import { createEvidenceBundle } from "../domain/evidenceBundle.js";
5
+ import { compareProcessCaptures, processCaptureSchema, } from "../domain/processCapture.js";
6
+ import { FUNCTION_COMPARISON_EXAMPLE } from "./functionComparisonExample.js";
7
+ import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "./processCaptureExample.js";
8
+ const comparison = compareFunctions(FUNCTION_COMPARISON_EXAMPLE.left, FUNCTION_COMPARISON_EXAMPLE.right, 0, 100);
9
+ export const FUNCTION_COMPARISON_EVIDENCE = createEvidence(undefined, {
10
+ id: "rea-function-comparison",
11
+ name: "REA function comparison",
12
+ version: "1",
13
+ }, {
14
+ predicateType: "rea.function-comparison/v1",
15
+ operation: "compare_functions",
16
+ parameters: {
17
+ left_evidence_ids: [FUNCTION_COMPARISON_EXAMPLE.left.evidence_id],
18
+ right_evidence_ids: [FUNCTION_COMPARISON_EXAMPLE.right.evidence_id],
19
+ offset: 0,
20
+ limit: 100,
21
+ },
22
+ result: jsonValueSchema.parse(comparison),
23
+ confidence: "derived",
24
+ authority: "analyst-inference",
25
+ evidenceLinks: [
26
+ FUNCTION_COMPARISON_EXAMPLE.left.evidence_id,
27
+ FUNCTION_COMPARISON_EXAMPLE.right.evidence_id,
28
+ ],
29
+ });
30
+ const PROCESS_PROVIDER = {
31
+ id: "rea-process",
32
+ name: "REA deterministic process harness",
33
+ version: "1",
34
+ };
35
+ const capture = processCaptureSchema.parse(EMPTY_PROCESS_CAPTURE_EXAMPLE);
36
+ const captureEvidence = (scenario) => createEvidence(undefined, PROCESS_PROVIDER, {
37
+ predicateType: "rea.process-capture/v2",
38
+ operation: "capture_process_scenario",
39
+ parameters: { scenario },
40
+ result: jsonValueSchema.parse(capture),
41
+ confidence: "observed",
42
+ authority: "controlled-replay",
43
+ environment: {
44
+ id: "fixture-linux",
45
+ platform: "linux",
46
+ architecture: "x86_64",
47
+ isolation: "container",
48
+ },
49
+ });
50
+ export const PROCESS_CAPTURE_REFERENCE = captureEvidence("reference");
51
+ export const PROCESS_CAPTURE_RECONSTRUCTION = captureEvidence("reconstruction");
52
+ export const PROCESS_COMPARISON_EVIDENCE = createEvidence(undefined, PROCESS_PROVIDER, {
53
+ predicateType: "rea.process-comparison/v1",
54
+ operation: "compare_process_captures",
55
+ parameters: {
56
+ left_evidence_id: PROCESS_CAPTURE_REFERENCE.evidence_id,
57
+ right_evidence_id: PROCESS_CAPTURE_RECONSTRUCTION.evidence_id,
58
+ left_normalization: capture.normalization,
59
+ right_normalization: capture.normalization,
60
+ },
61
+ result: jsonValueSchema.parse(compareProcessCaptures(capture, capture)),
62
+ confidence: "derived",
63
+ authority: "analyst-inference",
64
+ evidenceLinks: [
65
+ PROCESS_CAPTURE_REFERENCE.evidence_id,
66
+ PROCESS_CAPTURE_RECONSTRUCTION.evidence_id,
67
+ ],
68
+ });
69
+ /** Canonical inputs for comparison-composed investigation contracts. */
70
+ export const INVESTIGATION_EXAMPLES = {
71
+ find_changed_behavior: { comparisons: [FUNCTION_COMPARISON_EVIDENCE] },
72
+ build_call_path: {
73
+ functions: [FUNCTION_COMPARISON_EXAMPLE.left],
74
+ start: { address: "0x1000" },
75
+ goal: { address: "0x1000" },
76
+ },
77
+ correlate_static_and_runtime: {
78
+ static_comparisons: [FUNCTION_COMPARISON_EVIDENCE],
79
+ runtime_comparisons: [PROCESS_COMPARISON_EVIDENCE],
80
+ mappings: [
81
+ {
82
+ static: {
83
+ comparison_evidence_id: FUNCTION_COMPARISON_EVIDENCE.evidence_id,
84
+ selector: { kind: "function", dimension: "pseudocode" },
85
+ },
86
+ runtime: {
87
+ comparison_evidence_id: PROCESS_COMPARISON_EVIDENCE.evidence_id,
88
+ dimension: "terminal",
89
+ },
90
+ side_alignment: "left_to_left",
91
+ hypothesis: {
92
+ statement: "Static implementation changed without terminal change.",
93
+ expected_pattern: "static_only",
94
+ },
95
+ },
96
+ ],
97
+ },
98
+ verify_reconstruction: {
99
+ specification: {
100
+ schema_version: 1,
101
+ name: "Terminal compatibility",
102
+ claims: [
103
+ {
104
+ kind: "behavioral",
105
+ claim_id: "terminal-output",
106
+ title: "Terminal output remains equivalent",
107
+ comparison_evidence_id: PROCESS_COMPARISON_EVIDENCE.evidence_id,
108
+ dimension: "terminal",
109
+ },
110
+ ],
111
+ },
112
+ evidence_bundle: createEvidenceBundle([
113
+ PROCESS_CAPTURE_REFERENCE,
114
+ PROCESS_CAPTURE_RECONSTRUCTION,
115
+ PROCESS_COMPARISON_EVIDENCE,
116
+ ]),
117
+ },
118
+ };
@@ -0,0 +1,52 @@
1
+ import { z } from "zod";
2
+ import { nativeOutputSchemas } from "./toolOutputSchemas.js";
3
+ import { jsonValueSchema } from "../domain/jsonValue.js";
4
+ const examples = {
5
+ inspect_macho: {},
6
+ inspect_signature: {},
7
+ inspect_plist: { relative_path: "Contents/Info.plist" },
8
+ list_architectures: {},
9
+ demangle_swift: { symbols: ["$s4Test3fooyyF"] },
10
+ };
11
+ const native = (name, description, inputSchema) => {
12
+ const outputSchema = nativeOutputSchemas[name];
13
+ if (outputSchema === undefined)
14
+ throw new Error(`Missing native output schema for ${name}`);
15
+ return {
16
+ name,
17
+ description,
18
+ kind: "native-provider",
19
+ inputSchema,
20
+ outputSchema,
21
+ annotations: {
22
+ readOnlyHint: true,
23
+ destructiveHint: false,
24
+ idempotentHint: true,
25
+ openWorldHint: false,
26
+ },
27
+ examples: [
28
+ {
29
+ title: `Example ${name.replaceAll("_", " ")} request`,
30
+ input: z
31
+ .record(z.string(), jsonValueSchema)
32
+ .parse(examples[name] ?? {}),
33
+ },
34
+ ],
35
+ };
36
+ };
37
+ /** Provider-neutral semantic operations backed initially by macOS utilities. */
38
+ export const NATIVE_TOOL_CONTRACTS = [
39
+ native("inspect_macho", "Inspect Mach-O slices, load commands, imports, exports, dependencies, build metadata, segments, sections, permissions, and exact command provenance without launching Hopper.", z.object({})),
40
+ native("inspect_signature", "Inspect the active artifact's code-signing identity, hashes, authorities, requirements, entitlements, hardened-runtime state, and bounded command provenance.", z.object({})),
41
+ native("inspect_plist", "Parse a plist at a bounded relative path beneath the active artifact container. Symlink and traversal escapes are rejected; output is normalized JSON rather than plutil text.", z.object({
42
+ relative_path: z
43
+ .string()
44
+ .min(1)
45
+ .max(1_024)
46
+ .default("Contents/Info.plist"),
47
+ })),
48
+ native("list_architectures", "List thin or universal Mach-O slices with offsets, sizes, alignment, explicit coverage, and bounded native-tool provenance.", z.object({})),
49
+ native("demangle_swift", "Demangle an ordered bounded batch of Swift symbols without requiring Hopper. Each input returns demangled, unchanged, or invalid status.", z.object({
50
+ symbols: z.array(z.string().min(1).max(4_096)).min(1).max(500),
51
+ })),
52
+ ];
@@ -0,0 +1,25 @@
1
+ /** Minimal valid process capture used only in public contract examples. */
2
+ export const EMPTY_PROCESS_CAPTURE_EXAMPLE = {
3
+ schema_version: 2,
4
+ normalization: {
5
+ paths: true,
6
+ pids: true,
7
+ ports: true,
8
+ time_bucket_ms: 10,
9
+ patterns: [],
10
+ },
11
+ frames: [],
12
+ exit: { code: 0, signal: null, reason: "exited" },
13
+ process_samples: [],
14
+ protocol_events: [],
15
+ files_before: [],
16
+ files_after: [],
17
+ filesystem_effects: [],
18
+ truncated: false,
19
+ limitations: [],
20
+ residual_unknowns: [],
21
+ cleanup: {
22
+ owned_process_group: "verified",
23
+ temporary_root: "removed",
24
+ },
25
+ };
@@ -0,0 +1,69 @@
1
+ import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "./processCaptureExample.js";
2
+ import { UNKNOWN_CONTRACT_EXAMPLES } from "./unknownContractExamples.js";
3
+ import { ARTIFACT_COMPARISON_EXAMPLE } from "./artifactComparisonExample.js";
4
+ import { FUNCTION_COMPARISON_EXAMPLE } from "./functionComparisonExample.js";
5
+ import { INVESTIGATION_EXAMPLES } from "./investigationExamples.js";
6
+ /** Canonical examples for contracts whose required inputs have no defaults. */
7
+ export const TOOL_EXAMPLE_OVERRIDES = {
8
+ ...UNKNOWN_CONTRACT_EXAMPLES,
9
+ ...INVESTIGATION_EXAMPLES,
10
+ goto_address: { address: "0x1000" },
11
+ procedure_address: { procedure: "main" },
12
+ procedure_assembly: { procedure: "main" },
13
+ procedure_callees: { procedure: "main" },
14
+ procedure_callers: { procedure: "main" },
15
+ procedure_info: { procedure: "main" },
16
+ procedure_references: { procedure: "main" },
17
+ procedure_pseudo_code: { procedure: "main" },
18
+ resolve_containing_procedure: { address: "0x1000" },
19
+ search_procedures: { pattern: "main" },
20
+ search_strings: { pattern: "authorization failed" },
21
+ set_address_name: { address: "0x1000", name: "entry" },
22
+ set_addresses_names: { names: { "0x1000": "entry" } },
23
+ set_bookmark: { address: "0x1000" },
24
+ set_comment: { address: "0x1000", comment: "validated entry point" },
25
+ set_current_document: { document: "fixture" },
26
+ set_inline_comment: { address: "0x1000", comment: "calls parser" },
27
+ unset_bookmark: { address: "0x1000" },
28
+ get_call_graph: { address: "0x1000" },
29
+ find_xrefs_to_name: { name: "malloc" },
30
+ analyze_function: { procedure: "main" },
31
+ trace_feature: { query: "license" },
32
+ open_binary: { path: "/tmp/fixture" },
33
+ import_evidence_bundle: { path: "evidence.json" },
34
+ capture_process_scenario: {
35
+ approved: true,
36
+ executable: "/usr/bin/true",
37
+ working_directory: "/tmp",
38
+ },
39
+ compare_process_captures: {
40
+ left_evidence_id: `ev_${"0".repeat(64)}`,
41
+ left: EMPTY_PROCESS_CAPTURE_EXAMPLE,
42
+ right_evidence_id: `ev_${"1".repeat(64)}`,
43
+ right: EMPTY_PROCESS_CAPTURE_EXAMPLE,
44
+ },
45
+ compare_artifacts: ARTIFACT_COMPARISON_EXAMPLE,
46
+ compare_functions: FUNCTION_COMPARISON_EXAMPLE,
47
+ compare_bundles: {
48
+ left: {
49
+ bundle_version: 2,
50
+ artifacts: [],
51
+ providers: [],
52
+ environments: [],
53
+ scenarios: [],
54
+ captures: [],
55
+ unknowns: [],
56
+ records: [],
57
+ },
58
+ right: {
59
+ bundle_version: 2,
60
+ artifacts: [],
61
+ providers: [],
62
+ environments: [],
63
+ scenarios: [],
64
+ captures: [],
65
+ unknowns: [],
66
+ records: [],
67
+ },
68
+ },
69
+ };
@@ -1,13 +1,28 @@
1
1
  import { z } from "zod";
2
- import { evidenceBundleSchema } from "../domain/evidenceBundle.js";
2
+ import { jsonValueSchema } from "../domain/jsonValue.js";
3
3
  import { processCaptureSchema, processScenarioSchema, } from "../domain/processCapture.js";
4
+ import { recordUnknownInputSchema, updateUnknownInputSchema, } from "../domain/residualUnknown.js";
5
+ import { artifactComparisonInputSchema } from "../domain/artifactComparison.js";
6
+ import { functionComparisonInputSchema } from "../domain/functionComparison.js";
7
+ import { bundleComparisonInputSchema } from "../domain/bundleComparison.js";
8
+ import { changedBehaviorInputSchema } from "../domain/changedBehavior.js";
9
+ import { callPathInputSchema } from "../domain/callPath.js";
10
+ import { staticRuntimeCorrelationInputSchema } from "../domain/staticRuntimeCorrelation.js";
11
+ import { reconstructionVerificationInputSchema } from "../domain/reconstructionVerification.js";
4
12
  import { enhancedInputSchemas } from "./enhancedInputs.js";
5
- import { enhancedOutputSchemas, officialOutputSchemas, sessionOutputSchemas, } from "./toolOutputSchemas.js";
13
+ import { enhancedOutputSchemas, officialOutputSchemas, requireOutputSchema, sessionOutputSchemas, } from "./toolOutputSchemas.js";
14
+ import { TOOL_EXAMPLE_OVERRIDES } from "./toolContractExamples.js";
15
+ import { NATIVE_TOOL_CONTRACTS } from "./nativeToolContracts.js";
16
+ import { ARTIFACT_TOOL_CONTRACTS } from "./artifactToolContracts.js";
6
17
  const document = z.string().optional().describe("The document name");
7
18
  const address = z.string().describe("A Hopper address");
8
19
  const optionalAddress = address.optional();
9
20
  const procedure = z.string().describe("The procedure name or address");
10
- const pattern = z.string().describe("The regex pattern to search for");
21
+ const searchPattern = z
22
+ .string()
23
+ .min(1)
24
+ .max(256)
25
+ .describe("The literal text or bounded regex pattern to search for");
11
26
  const caseSensitive = z
12
27
  .boolean()
13
28
  .default(false)
@@ -16,29 +31,55 @@ const pagination = {
16
31
  offset: z.number().int().min(0).default(0),
17
32
  limit: z.number().int().min(1).max(500).default(100),
18
33
  };
34
+ const searchInput = {
35
+ pattern: searchPattern,
36
+ mode: z.enum(["literal", "regex"]).default("literal"),
37
+ case_sensitive: caseSensitive,
38
+ offset: z.number().int().min(0).default(0),
39
+ limit: z.number().int().min(1).max(100).default(100),
40
+ document,
41
+ };
42
+ const exampleInputSchema = z.record(z.string(), jsonValueSchema);
43
+ const examplesFor = (name, inputSchema) => {
44
+ const parsed = inputSchema.parse(TOOL_EXAMPLE_OVERRIDES[name] ?? {});
45
+ return [
46
+ {
47
+ title: `Example ${name.replaceAll("_", " ")} request`,
48
+ input: exampleInputSchema.parse(parsed),
49
+ },
50
+ ];
51
+ };
19
52
  const annotations = (name, kind) => ({
20
- readOnlyHint: kind === "enhanced" ||
21
- name === "export_evidence_bundle" ||
22
- (!name.startsWith("set_") &&
23
- name !== "unset_bookmark" &&
24
- name !== "goto_address" &&
25
- kind !== "session"),
26
- destructiveHint: name === "unset_bookmark" ||
53
+ readOnlyHint: (kind === "enhanced" && name !== "trace_feature") ||
54
+ name === "binary_session" ||
55
+ name === "list_unknowns" ||
56
+ name === "verify_unknown_resolution",
57
+ destructiveHint: name === "export_evidence_bundle" ||
58
+ name === "unset_bookmark" ||
27
59
  name === "set_address_name" ||
28
60
  name === "set_addresses_names" ||
29
61
  name === "set_comment" ||
30
62
  name === "set_inline_comment",
31
- idempotentHint: true,
32
- openWorldHint: false,
33
- });
34
- const official = (name, description, inputSchema) => ({
35
- name,
36
- description,
37
- kind: "official-proxy",
38
- inputSchema,
39
- outputSchema: requireOutputSchema(officialOutputSchemas, name),
40
- annotations: annotations(name, "official-proxy"),
63
+ idempotentHint: name !== "record_unknown" && name !== "update_unknown",
64
+ openWorldHint: name === "capture_process_scenario",
41
65
  });
66
+ const official = (name, description, inputSchema) => {
67
+ const trackedInputSchema = inputSchema.extend({
68
+ unknown_registry_approved: z
69
+ .literal(true)
70
+ .optional()
71
+ .describe("Explicit approval to record typed capability unavailability as a residual unknown"),
72
+ });
73
+ return {
74
+ name,
75
+ description,
76
+ kind: "official-proxy",
77
+ inputSchema: trackedInputSchema,
78
+ outputSchema: requireOutputSchema(officialOutputSchemas, name),
79
+ annotations: annotations(name, "official-proxy"),
80
+ examples: examplesFor(name, trackedInputSchema),
81
+ };
82
+ };
42
83
  const enhanced = (name, description, inputSchema) => ({
43
84
  name,
44
85
  description,
@@ -46,6 +87,7 @@ const enhanced = (name, description, inputSchema) => ({
46
87
  inputSchema,
47
88
  outputSchema: requireOutputSchema(enhancedOutputSchemas, name),
48
89
  annotations: annotations(name, "enhanced"),
90
+ examples: examplesFor(name, inputSchema),
49
91
  });
50
92
  const session = (name, description, inputSchema) => ({
51
93
  name,
@@ -54,13 +96,8 @@ const session = (name, description, inputSchema) => ({
54
96
  inputSchema,
55
97
  outputSchema: requireOutputSchema(sessionOutputSchemas, name),
56
98
  annotations: annotations(name, "session"),
99
+ examples: examplesFor(name, inputSchema),
57
100
  });
58
- const requireOutputSchema = (schemas, name) => {
59
- const schema = schemas[name];
60
- if (schema === undefined)
61
- throw new Error(`Missing output schema for ${name}`);
62
- return schema;
63
- };
64
101
  /** Bridge operations exposed without additional application composition. */
65
102
  export const OFFICIAL_TOOL_CONTRACTS = [
66
103
  official("address_name", "Resolve the analyzed name at a code or data address, defaulting to Hopper's current cursor. Use before following a symbol into xrefs; null means Hopper has no name at that address.", z.object({ document, address: optionalAddress })),
@@ -93,8 +130,8 @@ export const OFFICIAL_TOOL_CONTRACTS = [
93
130
  })),
94
131
  official("procedure_pseudo_code", "Decompile one analyzed procedure by symbol name or hexadecimal address. Returns Hopper pseudocode, not original source, and may return null; request procedure_assembly when instruction precision matters.", z.object({ procedure, document })),
95
132
  official("resolve_containing_procedure", "Resolve an arbitrary address, including an xref source or interior instruction, to its Hopper-analyzed containing procedure. A negative result includes an explicit reason and is not guessed from nearby symbols.", z.object({ address, document })),
96
- official("search_procedures", "Regex-search all analyzed procedure names with optional case sensitivity. The current bridge returns an unpaginated address/name map, so constrain patterns and use list_procedures for controlled exhaustive traversal.", z.object({ pattern, case_sensitive: caseSensitive, document })),
97
- official("search_strings", "Regex-search all analyzed strings with optional case sensitivity. The current bridge is unpaginated and evaluates Python regex, so use narrow patterns and follow matches with xrefs.", z.object({ pattern, case_sensitive: caseSensitive, document })),
133
+ official("search_procedures", "Search analyzed procedure names using literal matching by default or a structurally bounded regex. Returns a deterministic, offset-paginated page; continue at next_offset while has_more is true.", z.object(searchInput)),
134
+ official("search_strings", "Search analyzed strings using literal matching by default or a structurally bounded regex. Returns a deterministic, offset-paginated page with explicit value truncation; follow matches with xrefs.", z.object(searchInput)),
98
135
  official("set_address_name", "Assign an analyst name to one hexadecimal address and report Hopper's boolean result. This mutates analysis metadata; read it back with address_name before relying on it.", z.object({ address, name: z.string(), document })),
99
136
  official("set_addresses_names", "Assign analyst names to an address/name map and return per-address success booleans. This mutates analysis metadata; use for bounded batches and verify failures individually.", z.object({ names: z.record(z.string(), z.string()), document })),
100
137
  official("set_bookmark", "Create or replace a bookmark at a hexadecimal address. This mutates navigation metadata; verify with list_bookmarks and do not treat bookmarks as binary evidence.", z.object({ address, name: z.string().optional(), document })),
@@ -115,30 +152,56 @@ export const ENHANCED_TOOL_CONTRACTS = [
115
152
  enhanced("find_xrefs_to_name", "Resolve a name through Hopper and return analyzed references to its address. Use when starting from a selector or symbol; resolution failure is returned explicitly and xrefs remain untyped.", enhancedInputSchemas.find_xrefs_to_name),
116
153
  enhanced("binary_overview", "Use immediately after opening a target to summarize document, exhaustive procedure/string counts, and a bounded segment sample. detail controls segment fields and limit controls only the returned segment sample.", enhancedInputSchemas.binary_overview),
117
154
  enhanced("analyze_function", "Preferred bounded analysis for one procedure symbol or address. Returns identity, pseudocode, optional assembly, comments, calls, incoming references, and blocks; unsupported outgoing references and CFG edges carry explicit unavailable metadata.", enhancedInputSchemas.analyze_function),
118
- enhanced("trace_feature", "Trace a bounded literal feature query through matching strings and procedures, xrefs, and truthful containing-procedure resolution. Returns the operation budget, truncation, and residual unknowns; it does not infer reference kinds.", enhancedInputSchemas.trace_feature),
155
+ enhanced("trace_feature", "Trace a bounded literal feature query through matching strings and procedures, xrefs, and truthful containing-procedure resolution. Returns the operation budget, truncation, and residual unknowns; unknown_registry_approved: true records them durably without inferring reference kinds.", enhancedInputSchemas.trace_feature),
119
156
  ];
120
157
  /** Target lifecycle tools available only on the long-lived MCP adapter. */
121
158
  export const SESSION_TOOL_CONTRACTS = [
122
- session("open_binary", "Open a local executable, application bundle, or Hopper database, replacing the active target only after validation. This launches Hopper and may show UI; call binary_overview after success.", z.object({ path: z.string().min(1) })),
123
- session("close_binary", "Close the active Hopper-backed target and release its provider process. The operation is idempotent; call binary_session to verify the session is closed.", z.object({})),
124
- session("binary_session", "Report whether a target is open and, when open, its canonical path, format, and kind. Use before analysis calls or target switches; this performs no analysis.", z.object({})),
125
- session("export_evidence_bundle", "Return the session's deterministic Evidence v2 bundle without clearing it. Records are sorted by evidence ID, and array order carries no investigative meaning.", z.object({})),
126
- session("import_evidence_bundle", "Validate and atomically merge a local Evidence v2 bundle supplied as data. Semantic IDs are recomputed; tampering, unsupported versions, conflicts, and ledger overflow reject the entire import.", z.object({ bundle: evidenceBundleSchema })),
127
- session("capture_process_scenario", "Run one bounded process under a PTY using operator-approved executable and working roots. Requires approved: true on every call. Captures normalized terminal frames, sampled descendants, filesystem snapshots, and loopback HTTP/WebSocket replay. This launches a process and is disabled unless operator policy enables it; it is not a security sandbox.", processScenarioSchema),
159
+ session("open_binary", "Open a local executable, application bundle, archive, JavaScript, source map, plist, or Hopper database after validation. Providers start lazily: inventory_artifact does not launch Hopper; deep native operations may show Hopper UI.", z.object({ path: z.string().min(1) })),
160
+ session("close_binary", "Close the active target and every provider resource started for it. The operation is idempotent; call binary_session to verify the session is closed.", z.object({})),
161
+ session("binary_session", "Report provider identity, deterministic capability descriptors, and whether a target is open; open targets include canonical path, format, and kind. Use availability, effects, limits, and limitations before selecting analysis operations.", z.object({})),
162
+ session("export_evidence_bundle", "Return the session's deterministic Evidence v2 bundle, or atomically write it beneath an operator-approved root. Existing files require overwrite: true; records and manifests use canonical byte-stable ordering.", z.object({
163
+ path: z.string().min(1).optional(),
164
+ overwrite: z.boolean().default(false),
165
+ })),
166
+ session("import_evidence_bundle", "Read a bounded local JSON bundle beneath an operator-approved root, validate every Evidence v2 ID and canonical manifest, then atomically merge it. Imported content is data only and is never executed.", z.object({ path: z.string().min(1) })),
167
+ session("capture_process_scenario", "Run one bounded process under a PTY using operator-approved executable and working roots. Requires approved: true; unknown_registry_approved: true separately records capture residuals. Captures normalized terminal frames, descendants, filesystem snapshots, and loopback replay. Disabled unless operator policy enables it; not a security sandbox.", processScenarioSchema),
128
168
  session("compare_process_captures", "Compare two bounded process captures across terminal, exit, sampled process, filesystem, HTTP, and WebSocket evidence. Missing or truncated observations are never treated as equivalent.", z.object({
129
169
  left_evidence_id: z.string().regex(/^ev_[a-f0-9]{64}$/u),
130
170
  left: processCaptureSchema,
131
171
  right_evidence_id: z.string().regex(/^ev_[a-f0-9]{64}$/u),
132
172
  right: processCaptureSchema,
173
+ unknown_registry_approved: z
174
+ .literal(true)
175
+ .optional()
176
+ .describe("Explicit approval to record capture disagreement durably"),
177
+ })),
178
+ session("compare_artifacts", "Compare two bounded sets of inventory_artifact Evidence pages by logical occurrence path, content identity, metadata, and graph relations. Pages must share and satisfy their graph commitment; every delta cites both sets, and gaps yield truncated or unknown, never equivalence.", artifactComparisonInputSchema),
179
+ session("compare_functions", "Compare two explicit bounded sets of analyze_function Evidence pages across identity, exact provider text, calls, references, strings, and address-normalized CFG topology. Missing or provider-incompatible facets remain truncated or unknown; every conclusion cites both Evidence sets.", functionComparisonInputSchema),
180
+ session("compare_bundles", "Compare two canonical Evidence v2 bundles by exact record membership, explicit one-to-one observation pairs, and complete residual-unknown revision histories. Missing bundle members describe omission only, never behavioral equivalence; output is digest-anchored and deterministically paginated.", bundleComparisonInputSchema),
181
+ session("find_changed_behavior", "Aggregate validated process, artifact, and function comparison Evidence into a deterministic change report. Runtime observations remain distinct from static behavior candidates; missing or incomplete comparisons produce unresolved findings, never causal claims.", changedBehaviorInputSchema),
182
+ session("build_call_path", "Build bounded shortest-first direct-callee paths from explicit analyze_function Evidence groups using exact canonical addresses. Missing dossiers, incomplete callee pages, provider mixing, and depth frontiers remain unknown; every node and edge cites source Evidence.", callPathInputSchema),
183
+ session("correlate_static_and_runtime", "Evaluate explicit caller-declared hypotheses between exact static comparison findings and runtime comparison dimensions. Similar names or paths are never auto-matched, consistent cochange never proves causality, and unknown or truncated inputs remain unresolved.", staticRuntimeCorrelationInputSchema),
184
+ session("verify_reconstruction", "Verify a finite typed behavioral and structural specification against a canonical Evidence bundle. Pass means every declared claim has complete comparable authority—not global source equivalence; changed claims fail and missing, limited, or unresolved evidence stays unknown.", reconstructionVerificationInputSchema),
185
+ session("list_unknowns", "List current residual-unknown heads in deterministic ID order, with optional exact status, severity, and domain filters. This is read-only; unresolved, contradicted, and non-truth dispositions remain distinct.", z.object({
186
+ status: z
187
+ .enum(["open", "investigating", "blocked", "contradicted", "resolved"])
188
+ .optional(),
189
+ severity: z.enum(["low", "medium", "high", "critical"]).optional(),
190
+ domain: z.string().trim().min(1).max(100).optional(),
133
191
  })),
192
+ session("record_unknown", "Create one deterministic residual unknown and immutable mutation evidence. Requires approved: true, validates all evidence and relationship references, and rejects duplicate stable identity.", recordUnknownInputSchema),
193
+ session("update_unknown", "Append one immutable full-state revision and mutation evidence. Requires approved: true and exact expected_revision; stale concurrent writers fail instead of overwriting newer analysis.", updateUnknownInputSchema),
194
+ session("verify_unknown_resolution", "Revalidate the current residual-unknown head against live bundled evidence, exact authority/confidence/environment requirements, and revision integrity. Withdrawn and out-of-scope dispositions are not truth claims.", z.object({ unknown_id: z.string().regex(/^unk_[a-f0-9]{64}$/u) })),
134
195
  ];
135
196
  /**
136
197
  * Complete ordered public inventory used by registration and verification.
137
- * Keep this collection at 50 tools unless a deliberate contract change updates
198
+ * Keep this collection at 68 tools unless a deliberate contract change updates
138
199
  * snapshots, package verification, and real-Hopper verification together.
139
200
  */
140
201
  export const TOOL_CONTRACTS = [
141
202
  ...OFFICIAL_TOOL_CONTRACTS,
142
203
  ...ENHANCED_TOOL_CONTRACTS,
204
+ ...NATIVE_TOOL_CONTRACTS,
205
+ ...ARTIFACT_TOOL_CONTRACTS,
143
206
  ...SESSION_TOOL_CONTRACTS,
144
207
  ];