rea-agents 2.3.0 → 2.5.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 (209) hide show
  1. package/README.md +80 -18
  2. package/bridge/ghidra/ReaGhidraBridge.java +48 -1
  3. package/bridge/hopper_bridge.py +259 -24
  4. package/dist/application/AnalysisClientCleanup.js +14 -0
  5. package/dist/application/ApplicationWorkflowEvidenceResolver.js +31 -13
  6. package/dist/application/ArtifactExtraction.js +5 -1
  7. package/dist/application/ArtifactGraphConstruction.js +3 -15
  8. package/dist/application/ArtifactInventory/classify.js +5 -1
  9. package/dist/application/ArtifactInventory/reader.js +2 -0
  10. package/dist/application/BinarySession.js +15 -4
  11. package/dist/application/BinarySessionStatus.js +32 -1
  12. package/dist/application/BrowserEvidence.js +14 -0
  13. package/dist/application/BrowserScenarioCapturePort.js +1 -0
  14. package/dist/application/BrowserScenarioCaptureService.js +53 -0
  15. package/dist/application/BrowserScenarioEvidence.js +40 -0
  16. package/dist/application/CallPathTracing.js +160 -0
  17. package/dist/application/CapabilityInventory.js +79 -15
  18. package/dist/application/CompositeProvider.js +10 -1
  19. package/dist/application/DirectAnalysis.js +7 -2
  20. package/dist/application/EnhancedLiteralTracing.js +118 -0
  21. package/dist/application/EnhancedTools.js +35 -121
  22. package/dist/application/JavaScriptApplicationWorkflowEvidence.js +13 -0
  23. package/dist/application/JavaScriptApplicationWorkflowService.js +27 -1
  24. package/dist/application/JavaScriptRuntimeObservationEvidence.js +34 -0
  25. package/dist/application/JavaScriptRuntimeObservationPort.js +1 -0
  26. package/dist/application/JavaScriptRuntimeObservationService.js +46 -0
  27. package/dist/application/JavaScriptSemanticGraphAsyncProjection.js +147 -0
  28. package/dist/application/JavaScriptSemanticGraphBuilder.js +34 -6
  29. package/dist/application/JavaScriptSemanticGraphChildProcessProjection.js +156 -0
  30. package/dist/application/JavaScriptSemanticGraphConstruction.js +2 -2
  31. package/dist/application/JavaScriptSemanticGraphDataProjection.js +203 -0
  32. package/dist/application/JavaScriptSemanticGraphFingerprintProjection.js +25 -0
  33. package/dist/application/JavaScriptSemanticGraphFlowProjection.js +129 -8
  34. package/dist/application/JavaScriptSemanticGraphObjectProjection.js +81 -0
  35. package/dist/application/JavaScriptSemanticGraphProjection.js +14 -7
  36. package/dist/application/JavaScriptSemanticGraphResourceProjection.js +59 -0
  37. package/dist/application/JavaScriptSemanticGraphValueProjection.js +77 -0
  38. package/dist/application/ProcessCaptureEnvironment.js +26 -0
  39. package/dist/application/ProcessCaptureLifecycle.js +1 -0
  40. package/dist/application/ProcessCheckpoints.js +5 -0
  41. package/dist/application/ProcessHarness.js +45 -34
  42. package/dist/application/ProcessNormalization.js +6 -0
  43. package/dist/application/ProcessReactiveCoordinator.js +10 -3
  44. package/dist/application/ProcessReactiveEffects.js +15 -1
  45. package/dist/application/ProcessReactiveHarness.js +100 -0
  46. package/dist/application/ProcessReactiveObservations.js +101 -16
  47. package/dist/application/ProjectPermissionStore.js +1 -0
  48. package/dist/application/ReconstructionObligationAuthority.js +72 -0
  49. package/dist/application/ReconstructionObligationCandidates.js +265 -0
  50. package/dist/application/ReconstructionObligationLedgerDiagnostics.js +47 -0
  51. package/dist/application/ReconstructionObligationLedgerEvaluation.js +263 -0
  52. package/dist/application/ReconstructionObligationLedgerProjection.js +86 -0
  53. package/dist/application/ReconstructionObligationLedgerService.js +72 -0
  54. package/dist/application/ReconstructionObligationOriginalCases.js +33 -0
  55. package/dist/application/ReconstructionObligationPolicies.js +132 -0
  56. package/dist/application/ReconstructionReadinessService.js +48 -0
  57. package/dist/artifacts/ArtifactProvider.js +87 -22
  58. package/dist/browser/BrowserScenarioSecrets.js +53 -0
  59. package/dist/browser/BrowserScenarioSessionPort.js +1 -0
  60. package/dist/browser/CdpBrowserProvider.js +2 -2
  61. package/dist/browser/CdpCaptureDocuments.js +57 -0
  62. package/dist/browser/CdpCaptureStorage.js +71 -29
  63. package/dist/browser/CdpCaptureStorageFingerprints.js +328 -0
  64. package/dist/browser/CdpEndpoint.js +8 -6
  65. package/dist/browser/CdpPageCapture.js +2 -0
  66. package/dist/browser/JavaScriptRuntimeScope.js +38 -0
  67. package/dist/browser/PlaywrightBrowserScenarioProvider.js +253 -0
  68. package/dist/browser/PlaywrightExecutionBoundary.js +42 -0
  69. package/dist/browser/PlaywrightScenarioActions.js +62 -0
  70. package/dist/browser/PlaywrightScenarioArtifacts.js +238 -0
  71. package/dist/browser/PlaywrightScenarioBrowser.js +144 -0
  72. package/dist/browser/PlaywrightScenarioEvents.js +301 -0
  73. package/dist/browser/PlaywrightScenarioSession.js +300 -0
  74. package/dist/browser/V8InspectorCaptureProjection.js +110 -0
  75. package/dist/browser/V8InspectorEndpoint.js +72 -0
  76. package/dist/browser/V8InspectorProvider.js +267 -0
  77. package/dist/catalogIdentity.js +8 -0
  78. package/dist/cli/artifactCommands.js +39 -0
  79. package/dist/cli/coreAnalysisCommands.js +37 -0
  80. package/dist/cli.js +4 -0
  81. package/dist/cliApplicationCommands.js +30 -2
  82. package/dist/cliBrowserAdvancedCommands.js +17 -5
  83. package/dist/cliBrowserCommands.js +1 -0
  84. package/dist/cliBrowserScenarioCommands.js +43 -0
  85. package/dist/cliCommandNames.js +8 -0
  86. package/dist/cliJavaScriptRuntimeCommands.js +164 -0
  87. package/dist/cliObservationOptions.js +4 -0
  88. package/dist/cliPolicyCommands.js +1 -0
  89. package/dist/config/environment.js +12 -0
  90. package/dist/config/parseConfig.js +88 -17
  91. package/dist/contracts/analysisSearchContract.js +14 -0
  92. package/dist/contracts/applicationToolContracts.js +86 -2
  93. package/dist/contracts/applicationWorkflowInputContracts.js +18 -0
  94. package/dist/contracts/artifactToolContracts.js +22 -0
  95. package/dist/contracts/browserProviderToolContracts.js +7 -0
  96. package/dist/contracts/browserScenarioToolContracts.js +81 -0
  97. package/dist/contracts/browserToolContracts.js +88 -98
  98. package/dist/contracts/enhancedInputs.js +25 -6
  99. package/dist/contracts/functionInstructionContract.js +8 -0
  100. package/dist/contracts/hopperMemoryContracts.js +22 -0
  101. package/dist/contracts/javascriptApplicationWorkflowExamples.js +42 -0
  102. package/dist/contracts/javascriptRuntimeObservationToolContracts.js +58 -0
  103. package/dist/contracts/reconstructionReadinessExample.js +436 -0
  104. package/dist/contracts/toolContractExamples.js +5 -0
  105. package/dist/contracts/toolContractTypes.js +1 -0
  106. package/dist/contracts/toolContracts.js +13 -21
  107. package/dist/contracts/toolEffects.js +21 -0
  108. package/dist/contracts/toolOutputSchemaGroups.js +70 -20
  109. package/dist/contracts/toolOutputSchemaPrimitives.js +40 -0
  110. package/dist/domain/analysisErrorProjection.js +8 -0
  111. package/dist/domain/analysisSnapshot.js +2 -0
  112. package/dist/domain/artifactGraph.js +2 -0
  113. package/dist/domain/artifactInspection.js +347 -0
  114. package/dist/domain/binaryTarget.js +3 -6
  115. package/dist/domain/browserCaptureComparison.js +82 -0
  116. package/dist/domain/browserObservation.js +8 -1
  117. package/dist/domain/browserObservationErrors.js +1 -0
  118. package/dist/domain/browserObservationSchemas.js +26 -0
  119. package/dist/domain/browserScenario.js +165 -0
  120. package/dist/domain/browserScenarioCapture.js +57 -0
  121. package/dist/domain/browserScenarioCaptureValues.js +204 -0
  122. package/dist/domain/browserScenarioDiff.js +324 -0
  123. package/dist/domain/browserScenarioDiffValues.js +157 -0
  124. package/dist/domain/browserScenarioNormalization.js +45 -0
  125. package/dist/domain/browserScenarioValues.js +372 -0
  126. package/dist/domain/evidence.js +2 -0
  127. package/dist/domain/hopperValues.js +10 -0
  128. package/dist/domain/javascriptRuntimeObservation.js +170 -0
  129. package/dist/domain/javascriptRuntimeReconciliationParsing.js +120 -2
  130. package/dist/domain/javascriptRuntimeReconciliationResult.js +3 -3
  131. package/dist/domain/javascriptRuntimeReconciliationRuntime.js +2 -2
  132. package/dist/domain/javascriptRuntimeReconciliationSchemas.js +1 -1
  133. package/dist/domain/javascriptSemanticAnalysis.js +5 -4
  134. package/dist/domain/javascriptSemanticAsyncEffects.js +253 -0
  135. package/dist/domain/javascriptSemanticChildProcesses.js +237 -0
  136. package/dist/domain/javascriptSemanticDataEffectHelpers.js +125 -0
  137. package/dist/domain/javascriptSemanticDataEffects.js +284 -0
  138. package/dist/domain/javascriptSemanticDerivedAnalysis.js +41 -0
  139. package/dist/domain/javascriptSemanticFingerprints.js +174 -0
  140. package/dist/domain/javascriptSemanticIr.js +21 -1
  141. package/dist/domain/javascriptSemanticObjects.js +115 -0
  142. package/dist/domain/javascriptSemanticPromises.js +290 -0
  143. package/dist/domain/javascriptSemanticResources.js +136 -0
  144. package/dist/domain/javascriptStaticAnalysis.js +4 -3
  145. package/dist/domain/javascriptStaticAnalysisBundler.js +38 -0
  146. package/dist/domain/javascriptStaticAnalysisCalls.js +23 -1
  147. package/dist/domain/javascriptStaticAnalysisHelpers.js +8 -0
  148. package/dist/domain/processCapture.js +71 -1
  149. package/dist/domain/processCaptureReactiveValidation.js +175 -0
  150. package/dist/domain/processCaptureValidation.js +2 -0
  151. package/dist/domain/processObservation.js +15 -6
  152. package/dist/domain/processReactiveRuntime.js +21 -10
  153. package/dist/domain/processReactiveScenario.js +1 -0
  154. package/dist/domain/processReactiveTransition.js +17 -10
  155. package/dist/domain/processScenario.js +21 -0
  156. package/dist/domain/providerCleanupError.js +11 -0
  157. package/dist/domain/reconstructionObligationLedgerSchemas.js +278 -0
  158. package/dist/domain/reconstructionReadiness.js +121 -0
  159. package/dist/domain/reconstructionReadinessFindingHelpers.js +17 -0
  160. package/dist/domain/reconstructionReadinessIntegrityFindings.js +118 -0
  161. package/dist/domain/reconstructionReadinessJourneyFindings.js +193 -0
  162. package/dist/domain/reconstructionReadinessSchemas.js +240 -0
  163. package/dist/domain/sourceToBundleComparison.js +335 -0
  164. package/dist/domain/sourceToBundleComparisonSchemas.js +133 -0
  165. package/dist/domain/sourceToBundleSignals.js +202 -0
  166. package/dist/domain/webCaptureDiff.js +141 -27
  167. package/dist/domain/webScreenshot.js +1 -1
  168. package/dist/domain/zipPackageFormat.js +20 -0
  169. package/dist/generatedPackageMetadata.js +1 -1
  170. package/dist/ghidra/GhidraDefaults.js +1 -1
  171. package/dist/ghidra/GhidraFunctionValues.js +13 -1
  172. package/dist/ghidra/GhidraProviderCapabilities.js +7 -0
  173. package/dist/hopper/HopperCleanup.js +158 -0
  174. package/dist/hopper/HopperClient.js +69 -99
  175. package/dist/hopper/HopperProvider.js +20 -1
  176. package/dist/hopper/HopperRequestQueue.js +187 -0
  177. package/dist/hopper/HopperResponseStream.js +46 -0
  178. package/dist/hopper/protocol.js +1 -0
  179. package/dist/main/transport.js +24 -24
  180. package/dist/main.js +6 -0
  181. package/dist/mcpDoctor.js +25 -2
  182. package/dist/native/NativeMacOSProvider.js +1 -0
  183. package/dist/process/ProcessCommandIdentity.js +19 -0
  184. package/dist/process/ProcessOwnership.js +11 -8
  185. package/dist/server/DynamicToolAvailability.js +70 -0
  186. package/dist/server/createServer.js +46 -1
  187. package/dist/server/lifecycleProgress.js +10 -0
  188. package/dist/server/permissionFailure.js +12 -0
  189. package/dist/server/registerApplicationTools/compareSourceToBundle.js +35 -0
  190. package/dist/server/registerApplicationTools/helpers.js +11 -7
  191. package/dist/server/registerApplicationTools/obligationLedger.js +27 -0
  192. package/dist/server/registerApplicationTools/readiness.js +55 -0
  193. package/dist/server/registerApplicationTools.js +6 -0
  194. package/dist/server/registerArtifactTools.js +9 -1
  195. package/dist/server/registerBrowserScenarioTool.js +25 -0
  196. package/dist/server/registerBrowserTools.js +2 -2
  197. package/dist/server/registerCloseLifecycleTool.js +57 -0
  198. package/dist/server/registerEnhancedTools.js +16 -5
  199. package/dist/server/registerEvidenceResources.js +4 -0
  200. package/dist/server/registerEvidenceTools.js +9 -2
  201. package/dist/server/registerJavaScriptRuntimeObservationTools.js +37 -0
  202. package/dist/server/registerReconstructionObligationLedgerResource.js +39 -0
  203. package/dist/server/registerReconstructionReadinessResource.js +39 -0
  204. package/dist/server/registerSessionStatusTool.js +11 -8
  205. package/dist/server/registerSessionTools.js +11 -57
  206. package/dist/server/sessionAvailabilityPolicy.js +10 -8
  207. package/package.json +12 -6
  208. package/skills/reverse-engineer-anything/SKILL.md +4 -3
  209. package/skills/reverse-engineer-anything/references/native-and-artifacts.md +2 -2
@@ -1,8 +1,11 @@
1
1
  import { createHash } from "node:crypto";
2
+ import { isAbsolute, relative } from "node:path";
2
3
  import canonicalize from "canonicalize";
3
4
  import { z } from "zod";
4
- import { browserAllowedOriginsSchema, browserEndpointSchema, webPageInspectionSchema, } from "./browserObservation.js";
5
+ import { browserAllowedOriginsSchema, browserEndpointSchema, browserOriginSchema, webPageInspectionSchema, } from "./browserObservation.js";
6
+ import { classifyBrowserCompleteness, } from "./browserCompleteness.js";
5
7
  import { electronFileRootsSchema, electronPageInspectionSchema, } from "./electronObservation.js";
8
+ import { javascriptRuntimeObservationSchema, javascriptRuntimeKindSchema, } from "./javascriptRuntimeObservation.js";
6
9
  import { parseEvidence } from "./evidence.js";
7
10
  import { javascriptApplicationAnalysisResultSchema, } from "./javascriptApplicationAnalysis.js";
8
11
  import { compareCodePoints, } from "./javascriptApplicationGraph.js";
@@ -109,8 +112,123 @@ const parseRuntimeCapture = (input) => {
109
112
  scriptsCompleteWithinScope: scriptsComplete(inspection.completeness),
110
113
  };
111
114
  }
112
- throw new TypeError("Runtime reconciliation requires inspect_web_page or inspect_electron_page Evidence");
115
+ if (evidence.operation === "observe_javascript_runtime") {
116
+ assertEvidenceIdentity(evidence, {
117
+ operation: "observe_javascript_runtime",
118
+ predicate: "rea.javascript-runtime-observation/v1",
119
+ providerId: "rea-v8-inspector",
120
+ providerName: "REA passive Node/Electron V8 Inspector provider",
121
+ providerVersion: "1",
122
+ authority: "external-service",
123
+ confidence: "observed",
124
+ });
125
+ const result = javascriptRuntimeObservationSchema.parse(evidence.normalized_result);
126
+ assertV8RuntimeParameters(evidence, result);
127
+ const inspection = normalizeV8Inspection(result);
128
+ return {
129
+ kind: "v8-inspector",
130
+ evidence,
131
+ inspection,
132
+ captureSha256: digestCanonical(result),
133
+ scriptsCompleteWithinScope: false,
134
+ };
135
+ }
136
+ throw new TypeError("Runtime reconciliation requires inspect_web_page, inspect_electron_page, or observe_javascript_runtime Evidence");
137
+ };
138
+ const assertV8RuntimeParameters = (evidence, result) => {
139
+ const parameters = z
140
+ .object({
141
+ inspector_endpoint: browserEndpointSchema,
142
+ allowed_file_roots: z
143
+ .array(z.string().min(1).max(16_384).refine(isAbsolute))
144
+ .max(32),
145
+ allowed_origins: z.array(browserOriginSchema).max(32),
146
+ target_id: z.string().trim().min(1).max(256),
147
+ runtime_kind: javascriptRuntimeKindSchema,
148
+ })
149
+ .passthrough()
150
+ .parse(evidence.parameters);
151
+ if (parameters.target_id !== result.target.target_id ||
152
+ parameters.runtime_kind !== result.target.runtime_kind)
153
+ throw new TypeError("Runtime Evidence target or declared role disagrees with its result");
154
+ for (const location of [
155
+ result.target.location,
156
+ ...result.scripts.items.map(({ location }) => location),
157
+ ])
158
+ if ((location.kind === "file" &&
159
+ !parameters.allowed_file_roots.some((root) => isWithinRoot(root, location.file_path))) ||
160
+ (location.kind === "url" &&
161
+ !parameters.allowed_origins.includes(location.origin)) ||
162
+ (location.kind === "builtin" && !location.specifier.startsWith("node:")))
163
+ throw new TypeError("Runtime Evidence contains a location outside its recorded scope");
164
+ };
165
+ const isWithinRoot = (root, path) => {
166
+ const remainder = relative(root, path);
167
+ return (remainder === "" || (!remainder.startsWith("..") && !isAbsolute(remainder)));
168
+ };
169
+ const normalizeV8Inspection = (result) => ({
170
+ target: {
171
+ target_id: result.target.target_id,
172
+ type: result.target.protocol_type,
173
+ title: result.target.runtime_kind,
174
+ attached: result.target.attached,
175
+ ...runtimeLocation(result.target.location),
176
+ },
177
+ frames: [],
178
+ scripts: {
179
+ items: result.scripts.items.map((script) => ({
180
+ script_key: script.script_key,
181
+ frame_id: script.execution_context_key,
182
+ ...runtimeLocation(script.location),
183
+ cdp_hash: script.cdp_hash ?? "",
184
+ length: script.length,
185
+ is_module: script.is_module,
186
+ language: null,
187
+ source: {
188
+ included: false,
189
+ reason: "V8 Inspector source capture is outside passive authority.",
190
+ },
191
+ })),
192
+ },
193
+ workers: [],
194
+ completeness: classifyBrowserCompleteness({
195
+ policyFilteredSections: new Set(excludedV8Scripts(result) > 0 ? ["scripts"] : []),
196
+ attachLimitedSections: new Set([
197
+ "frames",
198
+ "scripts",
199
+ "script_sources",
200
+ "workers",
201
+ ]),
202
+ truncatedSections: new Set(result.capture.truncated ? ["scripts"] : []),
203
+ unavailableSections: new Set(["frames", "script_sources", "workers"]),
204
+ excluded: excludedV8Scripts(result) === 0
205
+ ? []
206
+ : [
207
+ {
208
+ section: "scripts",
209
+ reason: "out_of_target_scope",
210
+ count: excludedV8Scripts(result),
211
+ },
212
+ ],
213
+ droppedEvents: {
214
+ scripts: result.capture.events_dropped,
215
+ network_requests: 0,
216
+ console_events: 0,
217
+ websocket_connections: 0,
218
+ websocket_frames: 0,
219
+ webmcp_tools: 0,
220
+ timeline_events: 0,
221
+ },
222
+ }),
223
+ });
224
+ const runtimeLocation = (location) => {
225
+ if (location.kind === "file")
226
+ return { file_path: location.file_path };
227
+ if (location.kind === "url")
228
+ return { url: location.sanitized_url };
229
+ return { url: location.specifier };
113
230
  };
231
+ const excludedV8Scripts = (result) => Object.values(result.scripts.excluded).reduce((total, count) => total + count, 0);
114
232
  const assertRuntimeParameters = (evidence, expected) => {
115
233
  const common = {
116
234
  target_id: z.string().trim().min(1).max(256),
@@ -45,9 +45,9 @@ const captureSummaries = (captures, runtime) => captures.map((capture) => {
45
45
  kind: capture.kind,
46
46
  target_node_id: targetNode.node_id,
47
47
  target_key: capture.inspection.target.target_id,
48
- target_location: capture.kind === "browser"
49
- ? capture.inspection.target.url.slice(0, 4_096)
50
- : capture.inspection.target.file_path.slice(0, 4_096),
48
+ target_location: ("url" in capture.inspection.target
49
+ ? capture.inspection.target.url
50
+ : capture.inspection.target.file_path).slice(0, 4_096),
51
51
  frames: capture.inspection.frames.length,
52
52
  scripts: capture.inspection.scripts.items.length,
53
53
  workers: capture.inspection.workers.length,
@@ -197,7 +197,7 @@ const runtimeEvidence = (capture, input) => ({
197
197
  name: capture.evidence.provider.id,
198
198
  version: capture.evidence.provider.version ??
199
199
  (capture.kind === "browser" ? "2" : "1"),
200
- operation: capture.kind === "browser" ? "inspect_web_page" : "inspect_electron_page",
200
+ operation: capture.evidence.operation,
201
201
  executable_sha256: null,
202
202
  },
203
203
  coverage: runtimeSectionCoverage(capture, input.section),
@@ -260,7 +260,7 @@ const runtimeRelationshipEvidence = (capture, entity) => ({
260
260
  name: capture.evidence.provider.id,
261
261
  version: capture.evidence.provider.version ??
262
262
  (capture.kind === "browser" ? "2" : "1"),
263
- operation: capture.kind === "browser" ? "inspect_web_page" : "inspect_electron_page",
263
+ operation: capture.evidence.operation,
264
264
  executable_sha256: null,
265
265
  },
266
266
  coverage: runtimeSectionCoverage(capture, entity.section),
@@ -133,7 +133,7 @@ const layerSummarySchema = z.strictObject({
133
133
  const captureSummarySchema = z.strictObject({
134
134
  evidence_id: evidenceIdSchema,
135
135
  capture_sha256: digestSchema,
136
- kind: z.enum(["browser", "electron"]),
136
+ kind: z.enum(["browser", "electron", "v8-inspector"]),
137
137
  target_node_id: nodeIdSchema,
138
138
  target_key: boundedTextSchema,
139
139
  target_location: boundedTextSchema,
@@ -5,7 +5,7 @@ import { collectSemanticModuleLink, collectSemanticCallable, collectSemanticRefe
5
5
  import { currentSemanticScope, resolveSemanticBindingFromScope, semanticScopeId, semanticVariableScope, } from "./javascriptSemanticState.js";
6
6
  import { traverseJavaScriptAst } from "./javascriptSemanticTraversal.js";
7
7
  import { collectSemanticReturns, resolveSemanticModuleCallables, } from "./javascriptSemanticReturns.js";
8
- import { collectJavaScriptSemanticCalls } from "./javascriptSemanticCalls.js";
8
+ import { collectJavaScriptDerivedSemantics } from "./javascriptSemanticDerivedAnalysis.js";
9
9
  import { compareCodePoints, propertyName, range, } from "./javascriptStaticAnalysisHelpers.js";
10
10
  /** Recover bounded lexical bindings, aliases, constants, and module links. */
11
11
  export const analyzeJavaScriptSemantics = (source, inputLimits = {}) => {
@@ -28,16 +28,16 @@ export const analyzeJavaScriptSemantics = (source, inputLimits = {}) => {
28
28
  const bindings = immutableSemanticBindings(state);
29
29
  const callables = collectSemanticReturns(file.program, state, parserPartial);
30
30
  const moduleLinks = resolveSemanticModuleCallables(state, callables);
31
- const calls = collectJavaScriptSemanticCalls(file.program, state, callables);
31
+ const derived = collectJavaScriptDerivedSemantics(file.program, state, callables, parserPartial);
32
32
  return {
33
33
  schema: "JavaScriptSemanticIR",
34
- schemaVersion: 3,
34
+ schemaVersion: 4,
35
35
  scopes: immutableSemanticScopes(state),
36
36
  bindings,
37
37
  callables,
38
38
  references,
39
39
  moduleLinks,
40
- ...calls,
40
+ ...derived,
41
41
  coverage: {
42
42
  status: state.limitsReached.size > 0
43
43
  ? "truncated"
@@ -59,6 +59,7 @@ export const analyzeJavaScriptSemantics = (source, inputLimits = {}) => {
59
59
  "Values and aliases were recovered from inert syntax only; no JavaScript was executed.",
60
60
  "Return sites include only direct callable returns; nested callable returns remain separate.",
61
61
  "Local call, argument, return, and closure relations are static candidates and do not prove runtime invocation.",
62
+ "Promise ownership covers explicit unshadowed Promise construction, static factories, aggregation, chaining, and await syntax only.",
62
63
  "Cross-function mutation and dynamic property resolution remain unknown.",
63
64
  ],
64
65
  };
@@ -0,0 +1,253 @@
1
+ import * as t from "@babel/types";
2
+ import { reachSemanticLimit, semanticCallableIdForNode, semanticStaticPropertyName, } from "./javascriptSemanticProjection.js";
3
+ import { resolveSemanticBindingState, semanticResolutionBlocked, } from "./javascriptSemanticState.js";
4
+ import { traverseJavaScriptAst } from "./javascriptSemanticTraversal.js";
5
+ import { range } from "./javascriptStaticAnalysisHelpers.js";
6
+ const EVENT_REGISTER_METHODS = [
7
+ "on",
8
+ "once",
9
+ "addListener",
10
+ "prependListener",
11
+ "prependOnceListener",
12
+ ];
13
+ const EVENT_REMOVE_METHODS = [
14
+ "off",
15
+ "removeListener",
16
+ "removeAllListeners",
17
+ ];
18
+ const EVENT_METHODS = [
19
+ ...EVENT_REGISTER_METHODS,
20
+ ...EVENT_REMOVE_METHODS,
21
+ "emit",
22
+ ];
23
+ const TIMER_SCHEDULE_METHODS = [
24
+ "setTimeout",
25
+ "setInterval",
26
+ "setImmediate",
27
+ ];
28
+ const TIMER_CANCEL_METHODS = [
29
+ "clearTimeout",
30
+ "clearInterval",
31
+ "clearImmediate",
32
+ ];
33
+ /** Recover explicit EventEmitter and Node timer candidates from inert syntax. */
34
+ export const collectJavaScriptSemanticAsyncEffects = (program, state, callables) => {
35
+ const events = [];
36
+ const timers = [];
37
+ const callableIds = new Set(callables.map(({ callableId }) => callableId));
38
+ const callableStack = [];
39
+ traverseJavaScriptAst(program, {
40
+ enter: (node, parent) => {
41
+ const callableId = semanticCallableIdForNode(node);
42
+ if (callableId !== null && callableIds.has(callableId))
43
+ callableStack.push(callableId);
44
+ if (!t.isCallExpression(node) && !t.isOptionalCallExpression(node))
45
+ return;
46
+ const ownerCallableId = callableStack.at(-1) ?? null;
47
+ collectEvent(node, ownerCallableId, state, events);
48
+ collectTimer({ node, parent, ownerCallableId, state, output: timers });
49
+ },
50
+ exit: (node) => {
51
+ const callableId = semanticCallableIdForNode(node);
52
+ if (callableId !== null && callableStack.at(-1) === callableId)
53
+ callableStack.pop();
54
+ },
55
+ });
56
+ return {
57
+ eventOperations: events,
58
+ timerOperations: resolveTimers(timers),
59
+ };
60
+ };
61
+ const collectEvent = (node, ownerCallableId, state, output) => {
62
+ const member = memberCallee(node);
63
+ if (member === null)
64
+ return;
65
+ const method = EVENT_METHODS.find((candidate) => candidate ===
66
+ semanticStaticPropertyName(member.property, member.computed));
67
+ if (method === undefined)
68
+ return;
69
+ const kind = eventKind(method);
70
+ if (output.length >= state.limits.maxEventOperations) {
71
+ reachSemanticLimit(state, "maxEventOperations");
72
+ return;
73
+ }
74
+ const eventArgument = node.arguments[0];
75
+ const listenerArgument = node.arguments[1];
76
+ const emitter = emitterIdentity(member.object, state);
77
+ const eventName = literalString(eventArgument);
78
+ output.push({
79
+ eventId: `event:${kind}:${String(node.start ?? -1)}:${String(node.end ?? -1)}`,
80
+ kind,
81
+ method,
82
+ location: range(node),
83
+ ownerCallableId,
84
+ emitterKey: emitter.key,
85
+ emitterBindingId: emitter.bindingId,
86
+ eventName,
87
+ listenerBindingId: bindingId(listenerArgument, state),
88
+ listenerLocation: t.isNode(listenerArgument)
89
+ ? range(listenerArgument)
90
+ : null,
91
+ resolution: eventName === null
92
+ ? "unresolved"
93
+ : emitter.complete
94
+ ? "complete"
95
+ : "partial",
96
+ });
97
+ };
98
+ const eventKind = (method) => {
99
+ if (EVENT_REGISTER_METHODS.some((candidate) => candidate === method))
100
+ return "register";
101
+ if (EVENT_REMOVE_METHODS.some((candidate) => candidate === method))
102
+ return "remove";
103
+ return "dispatch";
104
+ };
105
+ const emitterIdentity = (node, state) => {
106
+ if (t.isIdentifier(node)) {
107
+ const binding = resolveSemanticBindingState(state, node, node.name);
108
+ if (binding !== undefined)
109
+ return {
110
+ key: `binding:${binding.bindingId}`,
111
+ bindingId: binding.bindingId,
112
+ complete: true,
113
+ };
114
+ return {
115
+ key: `global:${node.name}`,
116
+ bindingId: null,
117
+ complete: !semanticResolutionBlocked(state, node, node.name),
118
+ };
119
+ }
120
+ if (t.isThisExpression(node))
121
+ return { key: "this", bindingId: null, complete: true };
122
+ return {
123
+ key: `expression:${String(node.start ?? -1)}:${String(node.end ?? -1)}`,
124
+ bindingId: null,
125
+ complete: false,
126
+ };
127
+ };
128
+ const collectTimer = (input) => {
129
+ const { node, parent, ownerCallableId, state, output } = input;
130
+ const method = timerMethod(node.callee, state);
131
+ if (method === null)
132
+ return;
133
+ if (output.length >= state.limits.maxTimerOperations) {
134
+ reachSemanticLimit(state, "maxTimerOperations");
135
+ return;
136
+ }
137
+ const kind = TIMER_SCHEDULE_METHODS.some((candidate) => candidate === method)
138
+ ? "schedule"
139
+ : "cancel";
140
+ output.push({
141
+ node,
142
+ method,
143
+ kind,
144
+ ownerCallableId,
145
+ handleBindingId: kind === "schedule"
146
+ ? assignedBindingId(parent, node, state)
147
+ : bindingId(node.arguments[0], state),
148
+ });
149
+ };
150
+ const resolveTimers = (candidates) => {
151
+ const schedulesByBinding = new Map();
152
+ for (const candidate of candidates) {
153
+ if (candidate.kind !== "schedule" || candidate.handleBindingId === null)
154
+ continue;
155
+ const existing = schedulesByBinding.get(candidate.handleBindingId) ?? [];
156
+ existing.push(candidate);
157
+ schedulesByBinding.set(candidate.handleBindingId, existing);
158
+ }
159
+ return candidates.map((candidate) => {
160
+ const linked = candidate.kind === "cancel" && candidate.handleBindingId !== null
161
+ ? (schedulesByBinding.get(candidate.handleBindingId) ?? [])
162
+ : [];
163
+ return {
164
+ timerId: timerId(candidate),
165
+ kind: candidate.kind,
166
+ method: candidate.method,
167
+ location: range(candidate.node),
168
+ ownerCallableId: candidate.ownerCallableId,
169
+ handleBindingId: candidate.handleBindingId,
170
+ linkedTimerId: linked.length === 1 && linked[0] !== undefined
171
+ ? timerId(linked[0])
172
+ : null,
173
+ delayMilliseconds: timerDelay(candidate),
174
+ resolution: candidate.kind === "schedule"
175
+ ? "complete"
176
+ : linked.length === 1
177
+ ? "complete"
178
+ : linked.length > 1
179
+ ? "partial"
180
+ : "unresolved",
181
+ };
182
+ });
183
+ };
184
+ const timerMethod = (callee, state) => {
185
+ if (t.isIdentifier(callee)) {
186
+ const binding = resolveSemanticBindingState(state, callee, callee.name);
187
+ if (binding === undefined) {
188
+ const method = admittedTimerMethod(callee.name);
189
+ if (method === null)
190
+ return null;
191
+ return semanticResolutionBlocked(state, callee, callee.name)
192
+ ? null
193
+ : method;
194
+ }
195
+ return timerMethodFromBinding(binding);
196
+ }
197
+ if (!t.isMemberExpression(callee) && !t.isOptionalMemberExpression(callee))
198
+ return null;
199
+ const method = admittedTimerMethod(semanticStaticPropertyName(callee.property, callee.computed));
200
+ return method !== null &&
201
+ t.isIdentifier(callee.object) &&
202
+ timerNamespaceBinding(resolveSemanticBindingState(state, callee.object, callee.object.name))
203
+ ? method
204
+ : null;
205
+ };
206
+ const admittedTimerMethod = (value) => [...TIMER_SCHEDULE_METHODS, ...TIMER_CANCEL_METHODS].find((candidate) => candidate === value) ?? null;
207
+ const timerMethodFromBinding = (binding) => {
208
+ for (const { specifier, importedPath } of binding.directOrigins) {
209
+ if (!timerModule(specifier))
210
+ continue;
211
+ const method = admittedTimerMethod(importedPath.at(-1) ?? "");
212
+ if (method !== null)
213
+ return method;
214
+ }
215
+ return null;
216
+ };
217
+ const timerNamespaceBinding = (binding) => binding?.directOrigins.some(({ specifier, importedPath }) => timerModule(specifier) && importedPath.length === 0) ?? false;
218
+ const timerModule = (specifier) => specifier === "timers" || specifier === "node:timers";
219
+ const assignedBindingId = (parent, node, state) => {
220
+ const identifier = t.isVariableDeclarator(parent) &&
221
+ parent.init === node &&
222
+ t.isIdentifier(parent.id)
223
+ ? parent.id
224
+ : t.isAssignmentExpression(parent) &&
225
+ parent.right === node &&
226
+ t.isIdentifier(parent.left)
227
+ ? parent.left
228
+ : null;
229
+ return identifier === null
230
+ ? null
231
+ : (resolveSemanticBindingState(state, identifier, identifier.name)
232
+ ?.bindingId ?? null);
233
+ };
234
+ const bindingId = (node, state) => t.isIdentifier(node)
235
+ ? (resolveSemanticBindingState(state, node, node.name)?.bindingId ?? null)
236
+ : null;
237
+ const literalString = (node) => {
238
+ if (t.isStringLiteral(node))
239
+ return node.value;
240
+ if (t.isTemplateLiteral(node) && node.expressions.length === 0)
241
+ return node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw ?? null;
242
+ return null;
243
+ };
244
+ const timerDelay = (candidate) => {
245
+ if (candidate.kind !== "schedule" || candidate.method === "setImmediate")
246
+ return null;
247
+ const delay = candidate.node.arguments[1];
248
+ return t.isNumericLiteral(delay) ? delay.value : null;
249
+ };
250
+ const timerId = (candidate) => `timer:${candidate.kind}:${String(candidate.node.start ?? -1)}:${String(candidate.node.end ?? -1)}`;
251
+ const memberCallee = (node) => t.isMemberExpression(node.callee) || t.isOptionalMemberExpression(node.callee)
252
+ ? node.callee
253
+ : null;