rea-agents 2.1.0 → 2.3.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 (193) hide show
  1. package/README.md +84 -18
  2. package/dist/application/AndroidApplicationService.js +17 -0
  3. package/dist/application/AppleApplicationService.js +17 -0
  4. package/dist/application/ApplicationWorkflowEvidenceResolver.js +162 -0
  5. package/dist/application/BinarySession.js +9 -2
  6. package/dist/application/BinarySessionStatus.js +39 -1
  7. package/dist/application/CapabilityInventory.js +15 -0
  8. package/dist/application/ClientRegistrationStatus.js +11 -4
  9. package/dist/application/CommandShimReplay.js +6 -3
  10. package/dist/application/CompositeProvider.js +5 -2
  11. package/dist/application/DirectAnalysis.js +1 -20
  12. package/dist/application/DirectAnalysisStatus.js +83 -0
  13. package/dist/application/Doctor.js +34 -25
  14. package/dist/application/DoctorDiagnostics.js +1 -4
  15. package/dist/application/DoctorProjection.js +43 -0
  16. package/dist/application/DoctorScope.js +102 -0
  17. package/dist/application/EvidenceReferenceResolver.js +26 -0
  18. package/dist/application/InventoryProjectionEvidence.js +41 -0
  19. package/dist/application/InvestigationProviders.js +12 -0
  20. package/dist/application/JavaScriptApplicationEvidence.js +1 -1
  21. package/dist/application/JavaScriptApplicationEvidenceGraph.js +10 -1
  22. package/dist/application/JavaScriptApplicationService.js +3 -2
  23. package/dist/application/JavaScriptApplicationWorkflowEvidence.js +26 -0
  24. package/dist/application/JavaScriptApplicationWorkflowService.js +49 -1
  25. package/dist/application/JavaScriptArtifactReconstruction.js +7 -0
  26. package/dist/application/JavaScriptModuleRelationships.js +25 -1
  27. package/dist/application/JavaScriptReturnShapeProjection.js +111 -0
  28. package/dist/application/JavaScriptSemanticGraphBuilder.js +294 -0
  29. package/dist/application/JavaScriptSemanticGraphConstruction.js +144 -0
  30. package/dist/application/JavaScriptSemanticGraphEvidence.js +86 -0
  31. package/dist/application/JavaScriptSemanticGraphFlowProjection.js +72 -0
  32. package/dist/application/JavaScriptSemanticGraphProjection.js +33 -0
  33. package/dist/application/JavaScriptSemanticTraceService.js +45 -0
  34. package/dist/application/LoopbackReplay.js +277 -133
  35. package/dist/application/LoopbackReplayRecorder.js +101 -0
  36. package/dist/application/PermissionAuthority.js +7 -3
  37. package/dist/application/ProcessCaptureJournal.js +95 -0
  38. package/dist/application/ProcessCaptureLifecycle.js +24 -13
  39. package/dist/application/ProcessCheckpoints.js +30 -4
  40. package/dist/application/ProcessCli.js +26 -4
  41. package/dist/application/ProcessHarness.js +20 -50
  42. package/dist/application/ProcessNormalization.js +3 -2
  43. package/dist/application/ProcessPairedExperiment.js +72 -0
  44. package/dist/application/ProcessReactiveCoordinator.js +218 -0
  45. package/dist/application/ProcessReactiveEffects.js +133 -0
  46. package/dist/application/ProcessReactiveObservations.js +20 -0
  47. package/dist/application/ProcessSampling.js +2 -0
  48. package/dist/application/SessionProviderRouter.js +7 -6
  49. package/dist/application/Setup.js +14 -13
  50. package/dist/application/SetupClientConfiguration.js +10 -6
  51. package/dist/application/SetupPlan.js +4 -3
  52. package/dist/application/SetupRegistrationEnvironment.js +12 -0
  53. package/dist/application/SetupSkill.js +44 -40
  54. package/dist/application/TerminalRenderer.js +3 -1
  55. package/dist/application/Uninstall.js +7 -1
  56. package/dist/application/Upgrade.js +16 -1
  57. package/dist/browser/CdpEndpoint.js +37 -4
  58. package/dist/catalogIdentity.js +16 -7
  59. package/dist/cli/coreAnalysisCommands.js +34 -2
  60. package/dist/cli/javascriptApplicationAnalysis.js +19 -0
  61. package/dist/cli/setupCommands.js +84 -30
  62. package/dist/cli/utilityCommands.js +10 -2
  63. package/dist/cliApplicationCommands.js +79 -16
  64. package/dist/cliCommandNames.js +3 -0
  65. package/dist/cliElectronCommands.js +23 -33
  66. package/dist/cliObservationOptions.js +1 -1
  67. package/dist/cliOutput.js +56 -0
  68. package/dist/cliProcessCommands.js +32 -1
  69. package/dist/cliSetup.js +55 -29
  70. package/dist/contracts/applicationToolContracts.js +54 -7
  71. package/dist/contracts/applicationWorkflowInputContracts.js +119 -0
  72. package/dist/contracts/electronToolContracts.js +73 -4
  73. package/dist/contracts/javascriptApplicationWorkflowExamples.js +32 -9
  74. package/dist/contracts/managedToolContracts.js +7 -2
  75. package/dist/contracts/processCaptureExample.js +1 -0
  76. package/dist/contracts/replayMachineExample.js +37 -0
  77. package/dist/contracts/sessionStatusContract.js +26 -0
  78. package/dist/contracts/toolContractExamples.js +2 -0
  79. package/dist/contracts/toolContractTypes.js +12 -1
  80. package/dist/contracts/toolContracts.js +8 -11
  81. package/dist/contracts/toolEffects.js +3 -0
  82. package/dist/contracts/toolOutputSchemaGroups.js +53 -9
  83. package/dist/contracts/toolOutputSchemaPrimitives.js +73 -32
  84. package/dist/doctorRuntime.js +41 -0
  85. package/dist/domain/analysisErrorPresentation.js +5 -3
  86. package/dist/domain/analysisErrorProjection.js +7 -1
  87. package/dist/domain/androidApplication.js +206 -0
  88. package/dist/domain/appleApplication.js +221 -0
  89. package/dist/domain/boundedCartesianProjection.js +15 -0
  90. package/dist/domain/changedBehavior.js +5 -4
  91. package/dist/domain/completionLedgerGeneration.js +190 -0
  92. package/dist/domain/errors.js +4 -2
  93. package/dist/domain/evidenceCompletionLedger.js +113 -0
  94. package/dist/domain/hopperStartupFailure.js +9 -0
  95. package/dist/domain/javascriptApplicationAnalysis.js +44 -2
  96. package/dist/domain/javascriptExportShapeComparison.js +145 -0
  97. package/dist/domain/javascriptExportShapeComparisonIdentity.js +11 -0
  98. package/dist/domain/javascriptExportShapeComparisonSchemas.js +277 -0
  99. package/dist/domain/javascriptExportShapeSelection.js +132 -0
  100. package/dist/domain/javascriptExportShapeVariants.js +272 -0
  101. package/dist/domain/javascriptRuntimeReconciliationParsing.js +2 -2
  102. package/dist/domain/javascriptSemanticAnalysis.js +17 -36
  103. package/dist/domain/javascriptSemanticCallResolution.js +129 -0
  104. package/dist/domain/javascriptSemanticCalls.js +269 -0
  105. package/dist/domain/javascriptSemanticGraph.js +356 -0
  106. package/dist/domain/javascriptSemanticGraphSchemas.js +329 -0
  107. package/dist/domain/javascriptSemanticGraphSerialization.js +23 -0
  108. package/dist/domain/javascriptSemanticIr.js +14 -1
  109. package/dist/domain/javascriptSemanticPrimitives.js +36 -0
  110. package/dist/domain/javascriptSemanticProjection.js +36 -4
  111. package/dist/domain/javascriptSemanticProvenance.js +29 -0
  112. package/dist/domain/javascriptSemanticQuery.js +294 -0
  113. package/dist/domain/javascriptSemanticQueryAssessment.js +74 -0
  114. package/dist/domain/javascriptSemanticQueryIdentity.js +86 -0
  115. package/dist/domain/javascriptSemanticQueryRelations.js +15 -0
  116. package/dist/domain/javascriptSemanticQuerySchemas.js +188 -0
  117. package/dist/domain/javascriptSemanticReturns.js +187 -0
  118. package/dist/domain/javascriptSemanticState.js +32 -0
  119. package/dist/domain/javascriptSemanticTraceSchemas.js +14 -0
  120. package/dist/domain/javascriptSemanticValues.js +54 -70
  121. package/dist/domain/processCapture.js +36 -0
  122. package/dist/domain/processCaptureValidation.js +101 -12
  123. package/dist/domain/processComparison.js +96 -24
  124. package/dist/domain/processObservation.js +153 -0
  125. package/dist/domain/processPairedExperiment.js +128 -0
  126. package/dist/domain/processReactiveCheckpointDataflow.js +32 -0
  127. package/dist/domain/processReactiveMatching.js +210 -0
  128. package/dist/domain/processReactiveRuntime.js +109 -0
  129. package/dist/domain/processReactiveScenario.js +361 -0
  130. package/dist/domain/processReactiveScenarioPreflight.js +120 -0
  131. package/dist/domain/processReactiveTransition.js +159 -0
  132. package/dist/domain/processScenario.js +12 -0
  133. package/dist/domain/processTraceComparison.js +43 -0
  134. package/dist/domain/processTraceDimensionProjection.js +50 -0
  135. package/dist/domain/processTraceEvaluation.js +312 -0
  136. package/dist/domain/processTraceSpecification.js +373 -0
  137. package/dist/domain/reconstructionVerification.js +5 -4
  138. package/dist/domain/replayMachine.js +332 -0
  139. package/dist/domain/replayMachineRun.js +216 -0
  140. package/dist/domain/replayMachineRuntime.js +294 -0
  141. package/dist/domain/runtimeIdentification.js +210 -0
  142. package/dist/domain/staticRuntimeCorrelation.js +4 -2
  143. package/dist/evaluation/CodexAgentEval.js +156 -0
  144. package/dist/generatedPackageMetadata.js +2 -2
  145. package/dist/ghidra/GhidraClient.js +13 -2
  146. package/dist/ghidra/GhidraDiagnostics.js +1 -0
  147. package/dist/ghidra/GhidraLauncher.js +7 -3
  148. package/dist/ghidra/GhidraProvider.js +8 -1
  149. package/dist/hopper/BridgeLauncher.js +56 -14
  150. package/dist/hopper/HopperClient.js +23 -10
  151. package/dist/hopper/HopperDiagnostics.js +14 -0
  152. package/dist/hopper/HopperProcessDiagnostic.js +14 -0
  153. package/dist/hopper/HopperProvider.js +8 -1
  154. package/dist/hopper/LinuxPrivateDisplayDiagnostic.js +72 -0
  155. package/dist/hopper/LinuxPrivateDisplayProbe.js +217 -0
  156. package/dist/identity.js +2 -1
  157. package/dist/main/transport.js +2 -0
  158. package/dist/mcpDoctor.js +310 -0
  159. package/dist/process/ProcessOwnership.js +200 -53
  160. package/dist/process/ProviderRunLineage.js +21 -0
  161. package/dist/server/createServer.js +8 -2
  162. package/dist/server/javascriptApplicationResult.js +106 -0
  163. package/dist/server/registerApplicationTools/characterization.js +3 -3
  164. package/dist/server/registerApplicationTools/compareExportShapes.js +37 -0
  165. package/dist/server/registerApplicationTools/compareVersions.js +12 -6
  166. package/dist/server/registerApplicationTools/controlledReplay.js +2 -2
  167. package/dist/server/registerApplicationTools/coverage.js +3 -3
  168. package/dist/server/registerApplicationTools/helpers.js +37 -25
  169. package/dist/server/registerApplicationTools/traceFeature.js +9 -5
  170. package/dist/server/registerApplicationTools/traceSemantics.js +30 -0
  171. package/dist/server/registerApplicationTools.js +4 -0
  172. package/dist/server/registerElectronTools.js +19 -5
  173. package/dist/server/registerEvidenceResources.js +7 -1
  174. package/dist/server/registerJavaScriptApplicationGraphResource.js +69 -0
  175. package/dist/server/registerManagedTools.js +18 -1
  176. package/dist/server/registerProcessComparisonTool.js +17 -7
  177. package/dist/server/registerReplayMachineTool.js +20 -0
  178. package/dist/server/registerSessionStatusTool.js +95 -25
  179. package/dist/server/registerSessionTools.js +12 -7
  180. package/dist/server/sessionAvailabilityPolicy.js +2 -1
  181. package/dist/server/toolResult.js +10 -4
  182. package/dist/serverIdentity.js +19 -0
  183. package/package.json +62 -29
  184. package/scripts/hopper-demo-x11.py +459 -61
  185. package/scripts/prepack.mjs +55 -0
  186. package/scripts/prepare.mjs +28 -0
  187. package/scripts/rea.mjs +26 -5
  188. package/skills/reverse-engineer-anything/SKILL.md +69 -228
  189. package/skills/reverse-engineer-anything/references/controlled-replay.md +12 -0
  190. package/skills/reverse-engineer-anything/references/evidence-workflows.md +33 -0
  191. package/skills/reverse-engineer-anything/references/javascript-applications.md +35 -0
  192. package/skills/reverse-engineer-anything/references/native-and-artifacts.md +36 -0
  193. package/skills/reverse-engineer-anything/references/runtime-observation.md +24 -0
@@ -0,0 +1,23 @@
1
+ import canonicalize from "canonicalize";
2
+ import { z } from "zod";
3
+ import { javaScriptSemanticGraphSchema, } from "./javascriptSemanticGraph.js";
4
+ const canonicalJson = (value) => {
5
+ const encoded = canonicalize(value);
6
+ if (encoded === undefined)
7
+ throw new TypeError("JavaScript semantic graph could not canonicalize data");
8
+ return encoded;
9
+ };
10
+ /** Parse a stored semantic graph and reject unsupported versions or stale IDs. */
11
+ export const parseJavaScriptSemanticGraph = (input) => {
12
+ const envelope = z
13
+ .object({ schema: z.string(), schema_version: z.number() })
14
+ .passthrough()
15
+ .safeParse(input);
16
+ if (envelope.success &&
17
+ envelope.data.schema === "JavaScriptSemanticRelationGraph" &&
18
+ envelope.data.schema_version !== 1)
19
+ throw new TypeError(`Unsupported JavaScript Semantic Relation Graph schema version: ${String(envelope.data.schema_version)}`);
20
+ return javaScriptSemanticGraphSchema.parse(input);
21
+ };
22
+ /** Serialize a verified semantic graph as canonical JSON. */
23
+ export const serializeJavaScriptSemanticGraph = (input) => canonicalJson(parseJavaScriptSemanticGraph(input));
@@ -5,6 +5,13 @@ export const DEFAULT_JAVASCRIPT_SEMANTIC_LIMITS = {
5
5
  maxCallables: 20_000,
6
6
  maxReferences: 100_000,
7
7
  maxModuleLinks: 20_000,
8
+ maxReturnSites: 20_000,
9
+ maxCallSites: 20_000,
10
+ maxCallArguments: 100_000,
11
+ maxArgumentFlows: 100_000,
12
+ maxCallReturnFlows: 100_000,
13
+ maxClosureCaptures: 100_000,
14
+ maxFrontiers: 20_000,
8
15
  maxValueDepth: 16,
9
16
  maxUnionValues: 32,
10
17
  maxObjectProperties: 256,
@@ -16,12 +23,18 @@ export const semanticBinding = (ir, bindingId) => ir.bindings.find(({ bindingId:
16
23
  /** Fail-closed result when Babel cannot produce an inert syntax tree. */
17
24
  export const failedJavaScriptSemanticIr = () => ({
18
25
  schema: "JavaScriptSemanticIR",
19
- schemaVersion: 1,
26
+ schemaVersion: 3,
20
27
  scopes: [],
21
28
  bindings: [],
22
29
  callables: [],
23
30
  references: [],
24
31
  moduleLinks: [],
32
+ callSites: [],
33
+ argumentFlows: [],
34
+ callReturnFlows: [],
35
+ callResultFlows: [],
36
+ closureCaptures: [],
37
+ frontiers: [],
25
38
  coverage: { status: "failed", omittedCount: null, limitsReached: [] },
26
39
  limitations: [
27
40
  "JavaScript parsing failed; no semantic absence claim is available.",
@@ -0,0 +1,36 @@
1
+ import * as t from "@babel/types";
2
+ import { reachSemanticValueLimit, semanticPrimitiveKey, } from "./javascriptSemanticProvenance.js";
3
+ import { compareCodePoints } from "./javascriptStaticAnalysisHelpers.js";
4
+ /** Normalize one bounded collection of possible primitive values. */
5
+ export const semanticPrimitiveSet = (values, state) => {
6
+ const unique = [
7
+ ...new Map(values.map((value) => [semanticPrimitiveKey(value), value])).values(),
8
+ ].sort((left, right) => compareCodePoints(semanticPrimitiveKey(left), semanticPrimitiveKey(right)));
9
+ if (unique.length > state.limits.maxUnionValues) {
10
+ reachSemanticValueLimit(state, "maxUnionValues");
11
+ return {
12
+ status: "limit-reached",
13
+ reason: "maxUnionValues reached.",
14
+ };
15
+ }
16
+ const only = unique[0];
17
+ return unique.length === 1 && only !== undefined
18
+ ? { status: "literal", value: only }
19
+ : { status: "union", values: unique };
20
+ };
21
+ /** Read the primitive candidates retained in one lattice value. */
22
+ export const semanticPrimitiveCandidates = (value) => value.status === "literal"
23
+ ? [value.value]
24
+ : value.status === "union"
25
+ ? value.values
26
+ : null;
27
+ /** Parse one Babel primitive literal without evaluating code. */
28
+ export const semanticPrimitiveValue = (node) => {
29
+ if (t.isStringLiteral(node) || t.isNumericLiteral(node))
30
+ return { found: true, value: node.value };
31
+ if (t.isBooleanLiteral(node))
32
+ return { found: true, value: node.value };
33
+ if (t.isNullLiteral(node))
34
+ return { found: true, value: null };
35
+ return { found: false };
36
+ };
@@ -13,8 +13,9 @@ export const collectSemanticCallable = (input) => {
13
13
  reachSemanticLimit(state, "maxCallables");
14
14
  return;
15
15
  }
16
+ const callableId = semanticCallableIdForNode(node) ?? "callable:unknown:-1:-1";
16
17
  state.callables.push({
17
- callableId: `callable:${kind}:${String(node.start ?? -1)}:${String(node.end ?? -1)}`,
18
+ callableId,
18
19
  kind,
19
20
  name: callableName(node, parent),
20
21
  containerScopeId: containerScope.scopeId,
@@ -22,7 +23,15 @@ export const collectSemanticCallable = (input) => {
22
23
  ? null
23
24
  : bodyScope.scopeId,
24
25
  location: range(node),
26
+ returnSites: [],
27
+ returnCoverage: {
28
+ status: "partial",
29
+ retainedCount: 0,
30
+ omittedCount: null,
31
+ limitsReached: [],
32
+ },
25
33
  });
34
+ state.callableNodesById.set(callableId, node);
26
35
  };
27
36
  /** Collect one static import/export relationship for later composition. */
28
37
  export const collectSemanticModuleLink = (node, state) => {
@@ -46,6 +55,7 @@ export const collectSemanticModuleLink = (node, state) => {
46
55
  importedName: null,
47
56
  localName: defaultDeclarationName(node.declaration),
48
57
  exportedName: "default",
58
+ callableId: semanticCallableIdForNode(node.declaration),
49
59
  location: range(node),
50
60
  });
51
61
  else if (t.isVariableDeclarator(node))
@@ -61,7 +71,7 @@ export const collectSemanticReferences = (program, state) => {
61
71
  enter: (node, parent) => {
62
72
  if (!t.isIdentifier(node) || parent === null)
63
73
  return;
64
- const role = referenceRole(node, parent);
74
+ const role = semanticReferenceRole(node, parent);
65
75
  if (role === null)
66
76
  return;
67
77
  const key = `${String(node.start)}:${role}:${node.name}`;
@@ -205,6 +215,7 @@ const collectNamedExports = (node, state) => {
205
215
  importedName: null,
206
216
  localName,
207
217
  exportedName: localName,
218
+ callableId: declarationCallableId(node.declaration, localName),
208
219
  location: range(node.declaration),
209
220
  });
210
221
  for (const specifier of node.specifiers)
@@ -265,6 +276,7 @@ const collectCommonJsExport = (node, state) => {
265
276
  importedName: origin?.importedPath.at(-1) ?? null,
266
277
  localName: t.isIdentifier(node.right) ? node.right.name : null,
267
278
  exportedName,
279
+ callableId: semanticCallableIdForNode(node.right),
268
280
  location: range(node),
269
281
  });
270
282
  };
@@ -273,9 +285,29 @@ const addModuleLink = (state, link) => {
273
285
  reachSemanticLimit(state, "maxModuleLinks");
274
286
  return;
275
287
  }
276
- state.moduleLinks.push(link);
288
+ state.moduleLinks.push({ ...link, callableId: link.callableId ?? null });
289
+ };
290
+ /** Deterministic callable identity shared by collection and return recovery. */
291
+ export const semanticCallableIdForNode = (node) => {
292
+ const kind = callableKind(node);
293
+ return kind === undefined
294
+ ? null
295
+ : `callable:${kind}:${String(node.start ?? -1)}:${String(node.end ?? -1)}`;
296
+ };
297
+ const declarationCallableId = (declaration, localName) => {
298
+ if ((t.isFunctionDeclaration(declaration) ||
299
+ t.isClassDeclaration(declaration)) &&
300
+ declaration.id?.name === localName)
301
+ return semanticCallableIdForNode(declaration);
302
+ if (!t.isVariableDeclaration(declaration))
303
+ return null;
304
+ const declarator = declaration.declarations.find(({ id }) => t.isIdentifier(id, { name: localName }));
305
+ return declarator?.init === null || declarator?.init === undefined
306
+ ? null
307
+ : semanticCallableIdForNode(declarator.init);
277
308
  };
278
- const referenceRole = (node, parent) => {
309
+ /** Classify one identifier occurrence consistently with semantic references. */
310
+ export const semanticReferenceRole = (node, parent) => {
279
311
  if (isDeclarationIdentifier(node, parent) || isNonReferenceKey(node, parent))
280
312
  return null;
281
313
  if ((t.isAssignmentExpression(parent) && parent.left === node) ||
@@ -0,0 +1,29 @@
1
+ import { compareCodePoints } from "./javascriptStaticAnalysisHelpers.js";
2
+ /** Construct one immutable semantic provenance value. */
3
+ export const semanticProvenance = (status, origins, reason) => ({ status, origins, reason });
4
+ /** Normalize bounded exact module origins into one provenance classification. */
5
+ export const semanticOriginsProvenance = (origins, state) => {
6
+ const unique = uniqueSemanticOrigins(origins);
7
+ if (unique.length > state.limits.maxUnionValues)
8
+ return semanticLimitProvenance(state, "maxUnionValues");
9
+ return unique.length === 1
10
+ ? semanticProvenance("module", unique, null)
11
+ : semanticProvenance("ambiguous", unique, "Multiple module origins.");
12
+ };
13
+ /** Deduplicate and canonically order module origins. */
14
+ export const uniqueSemanticOrigins = (origins) => [
15
+ ...new Map(origins.map((origin) => [semanticOriginKey(origin), origin])).values(),
16
+ ].sort((left, right) => compareCodePoints(semanticOriginKey(left), semanticOriginKey(right)));
17
+ /** Mark and return an explicit provenance-limit value. */
18
+ export const semanticLimitProvenance = (state, limit) => {
19
+ reachSemanticValueLimit(state, limit);
20
+ return semanticProvenance("limit-reached", [], `${limit} reached.`);
21
+ };
22
+ /** Record one value-lattice limit against the shared semantic analysis state. */
23
+ export const reachSemanticValueLimit = (state, limit) => {
24
+ state.limitsReached.add(limit);
25
+ state.omittedCount += 1;
26
+ };
27
+ const semanticOriginKey = (origin) => `${origin.specifier}\0${origin.importedPath.join("\0")}`;
28
+ /** Canonically distinguish primitive values across type boundaries. */
29
+ export const semanticPrimitiveKey = (value) => `${value === null ? "null" : typeof value}:${JSON.stringify(value)}`;
@@ -0,0 +1,294 @@
1
+ import { compareCodePoints } from "./javascriptApplicationGraph.js";
2
+ import { isJavaScriptSemanticOwnershipRelation as ownershipRelation } from "./javascriptSemanticQueryRelations.js";
3
+ import { javaScriptSemanticQueryInputSchema, javaScriptSemanticQueryResultSchema, } from "./javascriptSemanticQuerySchemas.js";
4
+ import { assessJavaScriptSemanticQuery, } from "./javascriptSemanticQueryAssessment.js";
5
+ import { canonicalJavaScriptSemanticQueryJson as canonicalJson, createJavaScriptSemanticQueryCursor as createCursor, JavaScriptSemanticQueryCursorError, javaScriptSemanticQueryIdentifier as queryIdentifier, parseJavaScriptSemanticQueryCursor as parseCursor, resolveJavaScriptSemanticQuerySeeds as resolveSeeds, } from "./javascriptSemanticQueryIdentity.js";
6
+ /** Run one deterministic bounded traversal over a verified semantic graph. */
7
+ export const queryJavaScriptSemanticGraph = (graph, rawInput) => {
8
+ const input = javaScriptSemanticQueryInputSchema.parse(rawInput);
9
+ const queryId = queryIdentifier(graph, input);
10
+ const offset = parseCursor(input.cursor, queryId);
11
+ const seeds = resolveSeeds(graph, input);
12
+ const admission = admitSeeds(graph, seeds, input);
13
+ const retainedSeeds = admission.nodeIds;
14
+ const adjacency = buildAdjacency(graph.relations, input);
15
+ const traversal = traverse(graph, retainedSeeds, adjacency, input);
16
+ const frontier = uniqueFrontier([
17
+ ...traversal.frontier,
18
+ ...admission.frontier,
19
+ ]);
20
+ const page = createQueryPage({
21
+ graph,
22
+ traversal,
23
+ query: input,
24
+ queryId,
25
+ offset,
26
+ retainedSeeds,
27
+ });
28
+ const allRelevantUnknowns = relevantUnknownFrontiers(graph, traversal.nodeIds, input);
29
+ const relevantUnknowns = allRelevantUnknowns.slice(0, input.limits.max_unknowns);
30
+ const candidateRelations = relevantCandidateRelationCount(graph, traversal.nodeIds, input);
31
+ const expectedMatches = expectedMatchesFor(page.retainedNodes, page.retainedRelations, input);
32
+ const assessment = assessJavaScriptSemanticQuery({
33
+ graph,
34
+ totalSeeds: seeds.length,
35
+ retainedSeeds: retainedSeeds.length,
36
+ expectedMatches: expectedMatches.length,
37
+ hasExpectation: input.expected !== null,
38
+ frontier,
39
+ unknowns: relevantUnknowns,
40
+ candidateRelations,
41
+ unknownsTruncated: allRelevantUnknowns.length > relevantUnknowns.length,
42
+ });
43
+ return javaScriptSemanticQueryResultSchema.parse({
44
+ schema_version: 1,
45
+ query_id: queryId,
46
+ source_graph_id: graph.graph_id,
47
+ seed: input.seed,
48
+ direction: input.direction,
49
+ status: assessment.status,
50
+ seed_node_ids: retainedSeeds,
51
+ nodes: page.pageNodes,
52
+ relations: page.pageRelations,
53
+ unknowns: relevantUnknowns,
54
+ expected_match_node_ids: expectedMatches.map(({ node_id }) => node_id),
55
+ summary: {
56
+ total_seed_matches: seeds.length,
57
+ retained_seed_matches: retainedSeeds.length,
58
+ traversed_nodes: traversal.nodeIds.size,
59
+ traversed_relations: traversal.relationIds.size,
60
+ traversed_functions: traversal.functionIds.size,
61
+ traversed_modules: traversal.modules.size,
62
+ relevant_unknowns: allRelevantUnknowns.length,
63
+ retained_unknowns: relevantUnknowns.length,
64
+ },
65
+ coverage: assessment.coverage,
66
+ page: {
67
+ offset,
68
+ size: page.pageRelations.length,
69
+ next_cursor: page.nextCursor,
70
+ },
71
+ applied_limits: input.limits,
72
+ accepted_limit_ranges: assessment.acceptedLimitRanges,
73
+ limitations: assessment.limitations,
74
+ });
75
+ };
76
+ const createQueryPage = ({ graph, traversal, query, queryId, offset, retainedSeeds, }) => {
77
+ const retainedNodes = graph.nodes.filter(({ node_id }) => traversal.nodeIds.has(node_id));
78
+ const retainedRelations = graph.relations.filter(({ relation_id }) => traversal.relationIds.has(relation_id));
79
+ if (offset > retainedRelations.length)
80
+ throw new JavaScriptSemanticQueryCursorError("Semantic query cursor is beyond the retained result");
81
+ const pageRelations = retainedRelations.slice(offset, offset + query.limits.page_size);
82
+ const pageNodeIds = new Set([
83
+ ...retainedSeeds,
84
+ ...pageRelations.flatMap(({ source_node_id, target_node_id }) => [
85
+ source_node_id,
86
+ target_node_id,
87
+ ]),
88
+ ]);
89
+ const pageNodes = graph.nodes.filter(({ node_id }) => pageNodeIds.has(node_id));
90
+ const nextOffset = offset + pageRelations.length;
91
+ return {
92
+ retainedNodes,
93
+ retainedRelations,
94
+ pageRelations,
95
+ pageNodes,
96
+ nextCursor: nextOffset < retainedRelations.length
97
+ ? createCursor(queryId, nextOffset)
98
+ : null,
99
+ };
100
+ };
101
+ const admitSeeds = (graph, seeds, input) => {
102
+ const nodes = new Map(graph.nodes.map((node) => [node.node_id, node]));
103
+ const retained = [];
104
+ const functions = new Set();
105
+ const modules = new Set();
106
+ const frontier = [];
107
+ for (const nodeId of seeds) {
108
+ const node = nodes.get(nodeId);
109
+ if (node === undefined)
110
+ continue;
111
+ const reason = seedBlockedReason(node, {
112
+ retained,
113
+ functions,
114
+ modules,
115
+ input,
116
+ });
117
+ if (reason !== null) {
118
+ frontier.push({ node_id: nodeId, depth: 0, reason });
119
+ continue;
120
+ }
121
+ retained.push(nodeId);
122
+ retainOwners(node, functions, modules);
123
+ }
124
+ return { nodeIds: retained, frontier: uniqueFrontier(frontier) };
125
+ };
126
+ const seedBlockedReason = (node, bounds) => {
127
+ if (bounds.retained.length >= bounds.input.limits.max_seed_matches)
128
+ return "max-seed-matches";
129
+ if (bounds.retained.length >= bounds.input.limits.max_nodes)
130
+ return "max-nodes";
131
+ const functionId = functionOwner(node);
132
+ if (functionId !== null &&
133
+ !bounds.functions.has(functionId) &&
134
+ bounds.functions.size >= bounds.input.limits.max_functions)
135
+ return "max-functions";
136
+ if (!bounds.modules.has(node.identity.module_path) &&
137
+ bounds.modules.size >= bounds.input.limits.max_modules)
138
+ return "max-modules";
139
+ return null;
140
+ };
141
+ const buildAdjacency = (relations, input) => {
142
+ const adjacency = new Map();
143
+ const allowed = input.allowed_relations === undefined
144
+ ? null
145
+ : new Set(input.allowed_relations);
146
+ for (const relation of relations) {
147
+ if (allowed !== null && !allowed.has(relation.relation))
148
+ continue;
149
+ if (relation.resolution === "candidate" &&
150
+ !input.include_ambiguous_dynamic_edges)
151
+ continue;
152
+ addDirectedEntries(adjacency, relation, input.direction);
153
+ }
154
+ for (const entries of adjacency.values())
155
+ entries.sort((left, right) => compareCodePoints(`${left.relation.relation_id}\0${left.nextNodeId}`, `${right.relation.relation_id}\0${right.nextNodeId}`));
156
+ return adjacency;
157
+ };
158
+ const addDirectedEntries = (adjacency, relation, direction) => {
159
+ if (direction === "callers") {
160
+ if (relation.relation === "calls")
161
+ addEntry(adjacency, relation.target_node_id, relation.source_node_id, relation);
162
+ return;
163
+ }
164
+ if (direction === "ownership") {
165
+ if (!ownershipRelation(relation.relation))
166
+ return;
167
+ addEntry(adjacency, relation.source_node_id, relation.target_node_id, relation);
168
+ addEntry(adjacency, relation.target_node_id, relation.source_node_id, relation);
169
+ return;
170
+ }
171
+ if (direction === "backward-provenance")
172
+ addEntry(adjacency, relation.target_node_id, relation.source_node_id, relation);
173
+ else
174
+ addEntry(adjacency, relation.source_node_id, relation.target_node_id, relation);
175
+ };
176
+ const addEntry = (adjacency, from, nextNodeId, relation) => {
177
+ const entries = adjacency.get(from) ?? [];
178
+ entries.push({ relation, nextNodeId });
179
+ adjacency.set(from, entries);
180
+ };
181
+ const traverse = (graph, seeds, adjacency, input) => {
182
+ const nodes = new Map(graph.nodes.map((node) => [node.node_id, node]));
183
+ const nodeIds = new Set(seeds);
184
+ const relationIds = new Set();
185
+ const functionIds = new Set();
186
+ const modules = new Set();
187
+ const frontier = [];
188
+ for (const nodeId of nodeIds)
189
+ retainOwners(nodes.get(nodeId), functionIds, modules);
190
+ const depths = new Map([...nodeIds].map((nodeId) => [nodeId, 0]));
191
+ const queue = [...nodeIds];
192
+ const bounds = {
193
+ nodes,
194
+ nodeIds,
195
+ relationIds,
196
+ functionIds,
197
+ modules,
198
+ input,
199
+ };
200
+ for (let offset = 0; offset < queue.length; offset += 1) {
201
+ const current = queue[offset];
202
+ if (current === undefined)
203
+ continue;
204
+ const depth = depths.get(current) ?? 0;
205
+ for (const entry of adjacency.get(current) ?? []) {
206
+ const reason = blockedReason(entry, depth, bounds);
207
+ if (reason !== null) {
208
+ frontier.push({ node_id: current, depth, reason });
209
+ continue;
210
+ }
211
+ relationIds.add(entry.relation.relation_id);
212
+ if (nodeIds.has(entry.nextNodeId))
213
+ continue;
214
+ const node = nodes.get(entry.nextNodeId);
215
+ if (node === undefined)
216
+ continue;
217
+ nodeIds.add(entry.nextNodeId);
218
+ retainOwners(node, functionIds, modules);
219
+ depths.set(entry.nextNodeId, depth + 1);
220
+ queue.push(entry.nextNodeId);
221
+ }
222
+ }
223
+ return {
224
+ nodeIds,
225
+ relationIds,
226
+ functionIds,
227
+ modules,
228
+ frontier: uniqueFrontier(frontier),
229
+ };
230
+ };
231
+ const blockedReason = (entry, depth, bounds) => {
232
+ if (depth >= bounds.input.limits.max_depth)
233
+ return "max-depth";
234
+ if (!bounds.relationIds.has(entry.relation.relation_id) &&
235
+ bounds.relationIds.size >= bounds.input.limits.max_edges)
236
+ return "max-edges";
237
+ if (bounds.nodeIds.has(entry.nextNodeId))
238
+ return null;
239
+ if (bounds.nodeIds.size >= bounds.input.limits.max_nodes)
240
+ return "max-nodes";
241
+ const node = bounds.nodes.get(entry.nextNodeId);
242
+ if (node === undefined)
243
+ return null;
244
+ const functionId = functionOwner(node);
245
+ if (functionId !== null &&
246
+ !bounds.functionIds.has(functionId) &&
247
+ bounds.functionIds.size >= bounds.input.limits.max_functions)
248
+ return "max-functions";
249
+ if (!bounds.modules.has(node.identity.module_path) &&
250
+ bounds.modules.size >= bounds.input.limits.max_modules)
251
+ return "max-modules";
252
+ return null;
253
+ };
254
+ const retainOwners = (node, functions, modules) => {
255
+ if (node === undefined)
256
+ return;
257
+ const functionId = functionOwner(node);
258
+ if (functionId !== null)
259
+ functions.add(functionId);
260
+ modules.add(node.identity.module_path);
261
+ };
262
+ const functionOwner = (node) => node.kind === "function" ? node.node_id : node.function_node_id;
263
+ const uniqueFrontier = (frontier) => [
264
+ ...new Map(frontier.map((item) => [
265
+ `${item.node_id}\0${String(item.depth)}\0${item.reason}`,
266
+ item,
267
+ ])).values(),
268
+ ].sort((left, right) => compareCodePoints(canonicalJson(left), canonicalJson(right)));
269
+ const relevantUnknownFrontiers = (graph, nodeIds, input) => {
270
+ const allowed = input.allowed_relations === undefined
271
+ ? null
272
+ : new Set(input.allowed_relations);
273
+ return graph.unknowns.filter((unknown) => (unknown.node_id === null || nodeIds.has(unknown.node_id)) &&
274
+ (allowed === null ||
275
+ unknown.relation_kinds.some((relation) => allowed.has(relation))));
276
+ };
277
+ const relevantCandidateRelationCount = (graph, nodeIds, input) => {
278
+ const allowed = input.allowed_relations === undefined
279
+ ? null
280
+ : new Set(input.allowed_relations);
281
+ return graph.relations.filter((relation) => relation.resolution === "candidate" &&
282
+ (allowed === null || allowed.has(relation.relation)) &&
283
+ (nodeIds.has(relation.source_node_id) ||
284
+ nodeIds.has(relation.target_node_id))).length;
285
+ };
286
+ const expectedMatchesFor = (nodes, relations, input) => {
287
+ if (input.expected === null)
288
+ return [];
289
+ const classes = new Set(input.expected.classes);
290
+ const connected = new Set(relations.map((relation) => input.expected?.role === "source"
291
+ ? relation.target_node_id
292
+ : relation.source_node_id));
293
+ return nodes.filter(({ kind, node_id }) => classes.has(kind) && !connected.has(node_id));
294
+ };
@@ -0,0 +1,74 @@
1
+ import { compareCodePoints } from "./javascriptApplicationGraph.js";
2
+ /** Classify a bounded semantic traversal without promoting unknowns to absence. */
3
+ export const assessJavaScriptSemanticQuery = (input) => ({
4
+ status: queryStatus(input),
5
+ coverage: queryCoverage(input),
6
+ limitations: queryLimitations(input),
7
+ acceptedLimitRanges: {
8
+ max_seed_matches: { minimum: 1, maximum: 1_000 },
9
+ max_nodes: { minimum: 1, maximum: 50_000 },
10
+ max_edges: { minimum: 1, maximum: 100_000 },
11
+ max_depth: { minimum: 0, maximum: 64 },
12
+ max_functions: { minimum: 1, maximum: 10_000 },
13
+ max_modules: { minimum: 1, maximum: 10_000 },
14
+ max_unknowns: { minimum: 0, maximum: 10_000 },
15
+ page_size: { minimum: 1, maximum: 1_000 },
16
+ },
17
+ });
18
+ const queryCoverage = (input) => ({
19
+ status: input.graph.coverage.status === "unavailable"
20
+ ? "unavailable"
21
+ : input.frontier.length > 0 || input.unknownsTruncated
22
+ ? "truncated"
23
+ : input.unknowns.length > 0 ||
24
+ input.candidateRelations > 0 ||
25
+ input.graph.coverage.status !== "complete"
26
+ ? "partial"
27
+ : "complete",
28
+ frontier: [...input.frontier],
29
+ });
30
+ const queryStatus = (input) => {
31
+ if (input.graph.coverage.status === "unavailable")
32
+ return "unsupported";
33
+ if (input.frontier.length > 0 || input.unknownsTruncated)
34
+ return "truncated";
35
+ if (input.candidateRelations > 0)
36
+ return "ambiguous";
37
+ if (input.totalSeeds === 0 ||
38
+ (input.hasExpectation && input.expectedMatches === 0))
39
+ return input.unknowns.length > 0 ||
40
+ input.graph.coverage.status !== "complete"
41
+ ? "partial"
42
+ : "no-match";
43
+ return input.totalSeeds > 1 ? "ambiguous" : "found";
44
+ };
45
+ const queryLimitations = (input) => uniqueSorted([
46
+ "Semantic graph reachability is a static inference and does not prove runtime execution.",
47
+ ...input.graph.limitations,
48
+ ...(input.totalSeeds === input.retainedSeeds
49
+ ? []
50
+ : [
51
+ "Seed matches exceeded caller bounds; omitted starts remain unexplored.",
52
+ ]),
53
+ ...(input.frontier.length === 0
54
+ ? []
55
+ : [
56
+ "Traversal stopped at explicit caller limits; frontier facts remain unknown.",
57
+ ]),
58
+ ...(input.unknowns.length === 0
59
+ ? []
60
+ : [
61
+ "Relevant dynamic, unsupported, incomplete, or ambiguous semantics remain unknown.",
62
+ ]),
63
+ ...(input.unknownsTruncated
64
+ ? [
65
+ "Relevant unknown frontiers exceeded the caller limit; omitted unknowns remain unresolved.",
66
+ ]
67
+ : []),
68
+ ...(input.candidateRelations === 0
69
+ ? []
70
+ : [
71
+ "Candidate relations remain ambiguous and do not establish a resolved semantic path.",
72
+ ]),
73
+ ]);
74
+ const uniqueSorted = (values) => [...new Set(values)].sort(compareCodePoints);
@@ -0,0 +1,86 @@
1
+ import { createHash } from "node:crypto";
2
+ import canonicalize from "canonicalize";
3
+ import { compareCodePoints } from "./javascriptApplicationGraph.js";
4
+ /** Caller cursor that cannot identify a page of the current semantic query. */
5
+ export class JavaScriptSemanticQueryCursorError extends TypeError {
6
+ }
7
+ /** Canonical JSON used by semantic query commitments and ordering. */
8
+ export const canonicalJavaScriptSemanticQueryJson = (value) => {
9
+ const encoded = canonicalize(value);
10
+ if (encoded === undefined)
11
+ throw new TypeError("JavaScript semantic query could not canonicalize data");
12
+ return encoded;
13
+ };
14
+ const digest = (value) => createHash("sha256")
15
+ .update(canonicalJavaScriptSemanticQueryJson(value))
16
+ .digest("hex");
17
+ const uniqueSorted = (values) => [...new Set(values)].sort(compareCodePoints);
18
+ /** Derive the stable identity for one graph-bound query without its cursor. */
19
+ export const javaScriptSemanticQueryIdentifier = (graph, input) => {
20
+ const { cursor: _cursor, ...semantic } = input;
21
+ return `jsrq_${digest({
22
+ source_graph_id: graph.graph_id,
23
+ ...semantic,
24
+ allowed_relations: semantic.allowed_relations === undefined
25
+ ? undefined
26
+ : uniqueSorted(semantic.allowed_relations),
27
+ expected: semantic.expected === null
28
+ ? null
29
+ : {
30
+ ...semantic.expected,
31
+ classes: uniqueSorted(semantic.expected.classes),
32
+ },
33
+ })}`;
34
+ };
35
+ /** Commit one deterministic relation-page offset to a query identity. */
36
+ export const createJavaScriptSemanticQueryCursor = (queryId, offset) => `jsrqc_${String(offset)}_${digest({ query_id: queryId, offset })}`;
37
+ /** Parse a query-bound cursor and reject stale, malformed, or foreign values. */
38
+ export const parseJavaScriptSemanticQueryCursor = (cursor, queryId) => {
39
+ if (cursor === null)
40
+ return 0;
41
+ const match = /^jsrqc_([0-9]+)_([a-f0-9]{64})$/u.exec(cursor);
42
+ const offsetText = match?.[1];
43
+ const commitment = match?.[2];
44
+ if (offsetText === undefined || commitment === undefined)
45
+ throw new JavaScriptSemanticQueryCursorError("Semantic query cursor is malformed");
46
+ const offset = Number(offsetText);
47
+ if (!Number.isSafeInteger(offset) ||
48
+ offset < 0 ||
49
+ commitment !== digest({ query_id: queryId, offset }))
50
+ throw new JavaScriptSemanticQueryCursorError("Semantic query cursor does not match this query");
51
+ return offset;
52
+ };
53
+ /** Resolve deterministic seed candidates without applying traversal limits. */
54
+ export const resolveJavaScriptSemanticQuerySeeds = (graph, input) => {
55
+ const fingerprintFunctions = new Set(input.seed.kind === "function"
56
+ ? graph.fingerprints
57
+ .filter(({ fingerprint_sha256 }) => input.seed.kind === "function" &&
58
+ fingerprint_sha256 === input.seed.fingerprint_sha256)
59
+ .map(({ function_node_id }) => function_node_id)
60
+ : []);
61
+ return graph.nodes
62
+ .filter((node) => seedMatches(node, input, fingerprintFunctions))
63
+ .map(({ node_id }) => node_id)
64
+ .sort(compareCodePoints);
65
+ };
66
+ const seedMatches = (node, input, fingerprintFunctions) => {
67
+ const seed = input.seed;
68
+ if (seed.kind === "semantic-node")
69
+ return node.node_id === seed.node_id;
70
+ if (seed.kind === "application-node")
71
+ return node.application_node_ids.includes(seed.node_id);
72
+ if (seed.kind === "function")
73
+ return fingerprintFunctions.has(node.node_id);
74
+ if (seed.kind === "literal")
75
+ return (node.kind === "literal" &&
76
+ canonicalJavaScriptSemanticQueryJson(node.properties.value) ===
77
+ canonicalJavaScriptSemanticQueryJson(seed.value));
78
+ if (seed.kind === "property")
79
+ return node.kind === "property-slot" && node.properties.name === seed.name;
80
+ if (seed.kind === "endpoint")
81
+ return node.kind === "request" && node.properties.endpoint === seed.value;
82
+ if (seed.kind === "event")
83
+ return (["event", "listener"].includes(node.kind) &&
84
+ node.properties.event_name === seed.name);
85
+ return node.kind === "boundary" && node.properties.field === seed.field;
86
+ };