rea-agents 2.4.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.
- package/README.md +80 -18
- package/bridge/ghidra/ReaGhidraBridge.java +48 -1
- package/bridge/hopper_bridge.py +259 -24
- package/dist/application/AnalysisClientCleanup.js +14 -0
- package/dist/application/ApplicationWorkflowEvidenceResolver.js +31 -13
- package/dist/application/BinarySession.js +15 -4
- package/dist/application/BinarySessionStatus.js +31 -1
- package/dist/application/BrowserEvidence.js +14 -0
- package/dist/application/BrowserScenarioCapturePort.js +1 -0
- package/dist/application/BrowserScenarioCaptureService.js +53 -0
- package/dist/application/BrowserScenarioEvidence.js +40 -0
- package/dist/application/CallPathTracing.js +160 -0
- package/dist/application/CapabilityInventory.js +68 -12
- package/dist/application/CompositeProvider.js +10 -1
- package/dist/application/DirectAnalysis.js +7 -2
- package/dist/application/EnhancedLiteralTracing.js +118 -0
- package/dist/application/EnhancedTools.js +35 -121
- package/dist/application/JavaScriptApplicationWorkflowEvidence.js +13 -0
- package/dist/application/JavaScriptApplicationWorkflowService.js +27 -1
- package/dist/application/JavaScriptRuntimeObservationEvidence.js +34 -0
- package/dist/application/JavaScriptRuntimeObservationPort.js +1 -0
- package/dist/application/JavaScriptRuntimeObservationService.js +46 -0
- package/dist/application/JavaScriptSemanticGraphAsyncProjection.js +147 -0
- package/dist/application/JavaScriptSemanticGraphBuilder.js +34 -6
- package/dist/application/JavaScriptSemanticGraphChildProcessProjection.js +156 -0
- package/dist/application/JavaScriptSemanticGraphConstruction.js +2 -2
- package/dist/application/JavaScriptSemanticGraphDataProjection.js +203 -0
- package/dist/application/JavaScriptSemanticGraphFingerprintProjection.js +25 -0
- package/dist/application/JavaScriptSemanticGraphFlowProjection.js +129 -8
- package/dist/application/JavaScriptSemanticGraphObjectProjection.js +81 -0
- package/dist/application/JavaScriptSemanticGraphProjection.js +14 -7
- package/dist/application/JavaScriptSemanticGraphResourceProjection.js +59 -0
- package/dist/application/JavaScriptSemanticGraphValueProjection.js +77 -0
- package/dist/application/ProcessReactiveEffects.js +6 -0
- package/dist/application/ProcessReactiveHarness.js +16 -1
- package/dist/application/ProjectPermissionStore.js +1 -0
- package/dist/application/ReconstructionObligationAuthority.js +72 -0
- package/dist/application/ReconstructionObligationCandidates.js +265 -0
- package/dist/application/ReconstructionObligationLedgerDiagnostics.js +47 -0
- package/dist/application/ReconstructionObligationLedgerEvaluation.js +263 -0
- package/dist/application/ReconstructionObligationLedgerProjection.js +86 -0
- package/dist/application/ReconstructionObligationLedgerService.js +72 -0
- package/dist/application/ReconstructionObligationOriginalCases.js +33 -0
- package/dist/application/ReconstructionObligationPolicies.js +132 -0
- package/dist/application/ReconstructionReadinessService.js +48 -0
- package/dist/artifacts/ArtifactProvider.js +87 -22
- package/dist/browser/BrowserScenarioSecrets.js +53 -0
- package/dist/browser/BrowserScenarioSessionPort.js +1 -0
- package/dist/browser/CdpBrowserProvider.js +2 -2
- package/dist/browser/CdpCaptureDocuments.js +57 -0
- package/dist/browser/CdpCaptureStorage.js +71 -29
- package/dist/browser/CdpCaptureStorageFingerprints.js +328 -0
- package/dist/browser/CdpEndpoint.js +8 -6
- package/dist/browser/CdpPageCapture.js +2 -0
- package/dist/browser/JavaScriptRuntimeScope.js +38 -0
- package/dist/browser/PlaywrightBrowserScenarioProvider.js +253 -0
- package/dist/browser/PlaywrightExecutionBoundary.js +42 -0
- package/dist/browser/PlaywrightScenarioActions.js +62 -0
- package/dist/browser/PlaywrightScenarioArtifacts.js +238 -0
- package/dist/browser/PlaywrightScenarioBrowser.js +144 -0
- package/dist/browser/PlaywrightScenarioEvents.js +301 -0
- package/dist/browser/PlaywrightScenarioSession.js +300 -0
- package/dist/browser/V8InspectorCaptureProjection.js +110 -0
- package/dist/browser/V8InspectorEndpoint.js +72 -0
- package/dist/browser/V8InspectorProvider.js +267 -0
- package/dist/catalogIdentity.js +8 -0
- package/dist/cli/artifactCommands.js +39 -0
- package/dist/cli/coreAnalysisCommands.js +37 -0
- package/dist/cli.js +4 -0
- package/dist/cliApplicationCommands.js +30 -2
- package/dist/cliBrowserAdvancedCommands.js +17 -5
- package/dist/cliBrowserCommands.js +1 -0
- package/dist/cliBrowserScenarioCommands.js +43 -0
- package/dist/cliCommandNames.js +8 -0
- package/dist/cliJavaScriptRuntimeCommands.js +164 -0
- package/dist/cliObservationOptions.js +4 -0
- package/dist/cliPolicyCommands.js +1 -0
- package/dist/config/environment.js +12 -0
- package/dist/config/parseConfig.js +88 -17
- package/dist/contracts/analysisSearchContract.js +14 -0
- package/dist/contracts/applicationToolContracts.js +86 -2
- package/dist/contracts/applicationWorkflowInputContracts.js +18 -0
- package/dist/contracts/artifactToolContracts.js +22 -0
- package/dist/contracts/browserProviderToolContracts.js +7 -0
- package/dist/contracts/browserScenarioToolContracts.js +81 -0
- package/dist/contracts/browserToolContracts.js +88 -98
- package/dist/contracts/enhancedInputs.js +25 -6
- package/dist/contracts/functionInstructionContract.js +8 -0
- package/dist/contracts/hopperMemoryContracts.js +22 -0
- package/dist/contracts/javascriptApplicationWorkflowExamples.js +42 -0
- package/dist/contracts/javascriptRuntimeObservationToolContracts.js +58 -0
- package/dist/contracts/reconstructionReadinessExample.js +436 -0
- package/dist/contracts/toolContractExamples.js +5 -0
- package/dist/contracts/toolContractTypes.js +1 -0
- package/dist/contracts/toolContracts.js +13 -21
- package/dist/contracts/toolEffects.js +21 -0
- package/dist/contracts/toolOutputSchemaGroups.js +70 -20
- package/dist/contracts/toolOutputSchemaPrimitives.js +37 -0
- package/dist/domain/analysisErrorProjection.js +8 -0
- package/dist/domain/artifactInspection.js +347 -0
- package/dist/domain/browserCaptureComparison.js +82 -0
- package/dist/domain/browserObservation.js +8 -1
- package/dist/domain/browserObservationErrors.js +1 -0
- package/dist/domain/browserObservationSchemas.js +26 -0
- package/dist/domain/browserScenario.js +165 -0
- package/dist/domain/browserScenarioCapture.js +57 -0
- package/dist/domain/browserScenarioCaptureValues.js +204 -0
- package/dist/domain/browserScenarioDiff.js +324 -0
- package/dist/domain/browserScenarioDiffValues.js +157 -0
- package/dist/domain/browserScenarioNormalization.js +45 -0
- package/dist/domain/browserScenarioValues.js +372 -0
- package/dist/domain/hopperValues.js +10 -0
- package/dist/domain/javascriptRuntimeObservation.js +170 -0
- package/dist/domain/javascriptRuntimeReconciliationParsing.js +120 -2
- package/dist/domain/javascriptRuntimeReconciliationResult.js +3 -3
- package/dist/domain/javascriptRuntimeReconciliationRuntime.js +2 -2
- package/dist/domain/javascriptRuntimeReconciliationSchemas.js +1 -1
- package/dist/domain/javascriptSemanticAnalysis.js +5 -4
- package/dist/domain/javascriptSemanticAsyncEffects.js +253 -0
- package/dist/domain/javascriptSemanticChildProcesses.js +237 -0
- package/dist/domain/javascriptSemanticDataEffectHelpers.js +125 -0
- package/dist/domain/javascriptSemanticDataEffects.js +284 -0
- package/dist/domain/javascriptSemanticDerivedAnalysis.js +41 -0
- package/dist/domain/javascriptSemanticFingerprints.js +174 -0
- package/dist/domain/javascriptSemanticIr.js +21 -1
- package/dist/domain/javascriptSemanticObjects.js +115 -0
- package/dist/domain/javascriptSemanticPromises.js +290 -0
- package/dist/domain/javascriptSemanticResources.js +136 -0
- package/dist/domain/javascriptStaticAnalysis.js +4 -3
- package/dist/domain/javascriptStaticAnalysisBundler.js +38 -0
- package/dist/domain/javascriptStaticAnalysisCalls.js +23 -1
- package/dist/domain/javascriptStaticAnalysisHelpers.js +8 -0
- package/dist/domain/processCapture.js +8 -2
- package/dist/domain/processReactiveScenario.js +1 -0
- package/dist/domain/processReactiveTransition.js +9 -4
- package/dist/domain/providerCleanupError.js +11 -0
- package/dist/domain/reconstructionObligationLedgerSchemas.js +278 -0
- package/dist/domain/reconstructionReadiness.js +121 -0
- package/dist/domain/reconstructionReadinessFindingHelpers.js +17 -0
- package/dist/domain/reconstructionReadinessIntegrityFindings.js +118 -0
- package/dist/domain/reconstructionReadinessJourneyFindings.js +193 -0
- package/dist/domain/reconstructionReadinessSchemas.js +240 -0
- package/dist/domain/sourceToBundleComparison.js +335 -0
- package/dist/domain/sourceToBundleComparisonSchemas.js +133 -0
- package/dist/domain/sourceToBundleSignals.js +202 -0
- package/dist/domain/webCaptureDiff.js +141 -27
- package/dist/domain/webScreenshot.js +1 -1
- package/dist/generatedPackageMetadata.js +1 -1
- package/dist/ghidra/GhidraDefaults.js +1 -1
- package/dist/ghidra/GhidraFunctionValues.js +13 -1
- package/dist/ghidra/GhidraProviderCapabilities.js +7 -0
- package/dist/hopper/HopperCleanup.js +158 -0
- package/dist/hopper/HopperClient.js +69 -99
- package/dist/hopper/HopperProvider.js +20 -1
- package/dist/hopper/HopperRequestQueue.js +187 -0
- package/dist/hopper/HopperResponseStream.js +46 -0
- package/dist/hopper/protocol.js +1 -0
- package/dist/main/transport.js +24 -24
- package/dist/main.js +6 -0
- package/dist/mcpDoctor.js +25 -2
- package/dist/process/ProcessCommandIdentity.js +19 -0
- package/dist/process/ProcessOwnership.js +11 -8
- package/dist/server/DynamicToolAvailability.js +70 -0
- package/dist/server/createServer.js +46 -1
- package/dist/server/lifecycleProgress.js +10 -0
- package/dist/server/permissionFailure.js +12 -0
- package/dist/server/registerApplicationTools/compareSourceToBundle.js +35 -0
- package/dist/server/registerApplicationTools/helpers.js +11 -7
- package/dist/server/registerApplicationTools/obligationLedger.js +27 -0
- package/dist/server/registerApplicationTools/readiness.js +55 -0
- package/dist/server/registerApplicationTools.js +6 -0
- package/dist/server/registerArtifactTools.js +9 -1
- package/dist/server/registerBrowserScenarioTool.js +25 -0
- package/dist/server/registerBrowserTools.js +2 -2
- package/dist/server/registerCloseLifecycleTool.js +57 -0
- package/dist/server/registerEnhancedTools.js +16 -5
- package/dist/server/registerEvidenceResources.js +4 -0
- package/dist/server/registerEvidenceTools.js +9 -2
- package/dist/server/registerJavaScriptRuntimeObservationTools.js +37 -0
- package/dist/server/registerReconstructionObligationLedgerResource.js +39 -0
- package/dist/server/registerReconstructionReadinessResource.js +39 -0
- package/dist/server/registerSessionStatusTool.js +11 -8
- package/dist/server/registerSessionTools.js +11 -57
- package/dist/server/sessionAvailabilityPolicy.js +10 -8
- package/package.json +12 -6
- package/skills/reverse-engineer-anything/SKILL.md +2 -2
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import canonicalize from "canonicalize";
|
|
3
|
+
import { SOURCE_TO_BUNDLE_SIGNAL_WEIGHTS, sourceToBundleComparisonResultSchema, } from "./sourceToBundleComparisonSchemas.js";
|
|
4
|
+
import { buildSourceToBundleCandidateIndex, candidateIdsForSource, historicalSourceFiles, scoreSourceToBundleCandidate, sourceBearingNodes, } from "./sourceToBundleSignals.js";
|
|
5
|
+
/** Compare historical source files to static source-bearing application nodes. */
|
|
6
|
+
export const compareSourceToBundle = (input) => {
|
|
7
|
+
const projection = projectComparison(input);
|
|
8
|
+
const mappings = compareMappings(input, projection);
|
|
9
|
+
const factors = {
|
|
10
|
+
...projection.factors,
|
|
11
|
+
omittedCandidateEvaluations: mappings.omittedCandidateEvaluations,
|
|
12
|
+
omittedCandidateReferences: mappings.omittedCandidateReferences,
|
|
13
|
+
};
|
|
14
|
+
const mappedCurrentIds = new Set(mappings.items.flatMap(({ current_node_ids: nodeIds }) => nodeIds));
|
|
15
|
+
const unmappedCurrentNodeIds = projection.currentNodes
|
|
16
|
+
.map(({ node_id: nodeId }) => nodeId)
|
|
17
|
+
.filter((nodeId) => !mappedCurrentIds.has(nodeId));
|
|
18
|
+
const semanticResult = {
|
|
19
|
+
schema_version: 1,
|
|
20
|
+
reference: {
|
|
21
|
+
root_sha256: input.reference.root_sha256,
|
|
22
|
+
inventory_state: input.reference.inventory_state,
|
|
23
|
+
},
|
|
24
|
+
application: {
|
|
25
|
+
evidence_id: input.application.evidenceId,
|
|
26
|
+
graph_id: input.application.graph.graph_id,
|
|
27
|
+
root_artifact_sha256: input.application.rootArtifactSha256,
|
|
28
|
+
},
|
|
29
|
+
scoring: scoringModel(),
|
|
30
|
+
summary: comparisonSummary(mappings.items),
|
|
31
|
+
items: mappings.items,
|
|
32
|
+
unmapped_current_node_ids: unmappedCurrentNodeIds,
|
|
33
|
+
coverage: comparisonCoverage(projection, mappings, factors),
|
|
34
|
+
evidence_links: [input.application.evidenceId],
|
|
35
|
+
limitations: comparisonLimitations(factors),
|
|
36
|
+
};
|
|
37
|
+
return sourceToBundleComparisonResultSchema.parse({
|
|
38
|
+
...semanticResult,
|
|
39
|
+
comparison_id: `stbc_${digestCanonical(semanticResult)}`,
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
const projectComparison = (input) => {
|
|
43
|
+
const allSources = historicalSourceFiles(input.reference);
|
|
44
|
+
const allCurrentNodes = sourceBearingNodes(input.application.graph.nodes);
|
|
45
|
+
const sources = allSources.slice(0, input.limits.max_source_files);
|
|
46
|
+
const currentNodes = allCurrentNodes.slice(0, input.limits.max_application_nodes);
|
|
47
|
+
const omittedSourceFiles = allSources.length - sources.length;
|
|
48
|
+
const omittedApplicationNodes = allCurrentNodes.length - currentNodes.length;
|
|
49
|
+
const index = buildSourceToBundleCandidateIndex(currentNodes);
|
|
50
|
+
return {
|
|
51
|
+
sources,
|
|
52
|
+
currentNodes,
|
|
53
|
+
index,
|
|
54
|
+
absenceComplete: input.reference.inventory_state === "complete" &&
|
|
55
|
+
input.application.graph.coverage.status === "complete" &&
|
|
56
|
+
!input.application.graph.coverage.truncated &&
|
|
57
|
+
omittedSourceFiles === 0 &&
|
|
58
|
+
omittedApplicationNodes === 0 &&
|
|
59
|
+
!index.pathIndexTruncated,
|
|
60
|
+
factors: {
|
|
61
|
+
input,
|
|
62
|
+
omittedSourceFiles,
|
|
63
|
+
omittedApplicationNodes,
|
|
64
|
+
omittedCandidateEvaluations: 0,
|
|
65
|
+
omittedCandidateReferences: 0,
|
|
66
|
+
pathIndexTruncated: index.pathIndexTruncated,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
const compareMappings = (input, projection) => {
|
|
71
|
+
let evaluationsRemaining = input.limits.max_candidate_evaluations;
|
|
72
|
+
let candidateEvaluations = 0;
|
|
73
|
+
let omittedCandidateEvaluations = 0;
|
|
74
|
+
let omittedCandidateReferences = 0;
|
|
75
|
+
const provisionalItems = [];
|
|
76
|
+
for (const source of projection.sources) {
|
|
77
|
+
const candidateIds = candidateIdsForSource(source, projection.index);
|
|
78
|
+
const retainedIds = candidateIds.slice(0, evaluationsRemaining);
|
|
79
|
+
const omittedEvaluations = candidateIds.length - retainedIds.length;
|
|
80
|
+
evaluationsRemaining -= retainedIds.length;
|
|
81
|
+
candidateEvaluations += retainedIds.length;
|
|
82
|
+
omittedCandidateEvaluations += omittedEvaluations;
|
|
83
|
+
const scored = retainedIds
|
|
84
|
+
.map((nodeId) => scoreSourceToBundleCandidate(source, nodeId, projection.index))
|
|
85
|
+
.filter((candidate) => candidate !== null)
|
|
86
|
+
.sort(compareCandidates);
|
|
87
|
+
const retainedCandidates = scored.slice(0, input.limits.max_candidate_nodes);
|
|
88
|
+
const omittedCandidates = omittedEvaluations + scored.length - retainedCandidates.length;
|
|
89
|
+
omittedCandidateReferences += omittedCandidates;
|
|
90
|
+
const classification = classifyMapping(retainedCandidates, omittedCandidates === 0, projection.absenceComplete);
|
|
91
|
+
const semantic = {
|
|
92
|
+
source_path: source.path,
|
|
93
|
+
source_sha256: source.sha256,
|
|
94
|
+
source_language: source.language,
|
|
95
|
+
status: classification.status,
|
|
96
|
+
confidence: classification.confidence,
|
|
97
|
+
current_node_ids: classification.currentNodeIds,
|
|
98
|
+
candidates: retainedCandidates,
|
|
99
|
+
omitted_candidates: omittedCandidates,
|
|
100
|
+
limitations: uniqueSorted([
|
|
101
|
+
...source.limitations,
|
|
102
|
+
...classification.limitations,
|
|
103
|
+
...(omittedCandidates > 0
|
|
104
|
+
? [
|
|
105
|
+
`${String(omittedCandidates)} candidate references were omitted by comparison limits.`,
|
|
106
|
+
]
|
|
107
|
+
: []),
|
|
108
|
+
]),
|
|
109
|
+
};
|
|
110
|
+
provisionalItems.push({
|
|
111
|
+
mapping_id: mappingId(input, semantic),
|
|
112
|
+
...semantic,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
items: markMergedMappings(input, provisionalItems),
|
|
117
|
+
candidateEvaluations,
|
|
118
|
+
omittedCandidateEvaluations,
|
|
119
|
+
omittedCandidateReferences,
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
const comparisonCoverage = (projection, mappings, factors) => ({
|
|
123
|
+
status: coverageStatus(factors),
|
|
124
|
+
reference_inventory_state: factors.input.reference.inventory_state,
|
|
125
|
+
application_graph_status: factors.input.application.graph.coverage.status,
|
|
126
|
+
retained_source_files: projection.sources.length,
|
|
127
|
+
omitted_source_files: factors.omittedSourceFiles,
|
|
128
|
+
retained_application_nodes: projection.currentNodes.length,
|
|
129
|
+
omitted_application_nodes: factors.omittedApplicationNodes,
|
|
130
|
+
candidate_evaluations: mappings.candidateEvaluations,
|
|
131
|
+
omitted_candidate_evaluations: mappings.omittedCandidateEvaluations,
|
|
132
|
+
omitted_candidate_references: mappings.omittedCandidateReferences,
|
|
133
|
+
omitted_unmapped_current_nodes: 0,
|
|
134
|
+
});
|
|
135
|
+
const scoringModel = () => ({
|
|
136
|
+
algorithm: "rea-source-to-bundle-signals/v1",
|
|
137
|
+
minimum_candidate_score: 20,
|
|
138
|
+
weights: Object.entries(SOURCE_TO_BUNDLE_SIGNAL_WEIGHTS).map(([signal, weight]) => ({
|
|
139
|
+
signal: signal,
|
|
140
|
+
weight,
|
|
141
|
+
})),
|
|
142
|
+
});
|
|
143
|
+
const classifyMapping = (candidates, evaluationComplete, absenceComplete) => {
|
|
144
|
+
if (!evaluationComplete)
|
|
145
|
+
return {
|
|
146
|
+
status: "unknown",
|
|
147
|
+
confidence: "unknown",
|
|
148
|
+
currentNodeIds: [],
|
|
149
|
+
limitations: [
|
|
150
|
+
"Candidate evaluation stopped at the declared limit; a stronger match may be omitted.",
|
|
151
|
+
],
|
|
152
|
+
};
|
|
153
|
+
const exact = candidates.filter((candidate) => hasSignal(candidate, "exact-source-digest"));
|
|
154
|
+
if (exact.length === 1)
|
|
155
|
+
return {
|
|
156
|
+
status: "unchanged",
|
|
157
|
+
confidence: "exact",
|
|
158
|
+
currentNodeIds: [exact[0]?.current_node_id ?? ""].filter(Boolean),
|
|
159
|
+
limitations: [
|
|
160
|
+
"Exact byte identity does not establish equivalent load context or runtime behavior.",
|
|
161
|
+
],
|
|
162
|
+
};
|
|
163
|
+
if (exact.length > 1)
|
|
164
|
+
return {
|
|
165
|
+
status: "duplicated",
|
|
166
|
+
confidence: "exact",
|
|
167
|
+
currentNodeIds: exact.map(({ current_node_id: nodeId }) => nodeId),
|
|
168
|
+
limitations: [
|
|
169
|
+
"The same historical source digest occurs in multiple current graph nodes.",
|
|
170
|
+
],
|
|
171
|
+
};
|
|
172
|
+
const pathCandidates = candidates.filter((candidate) => candidate.signals.some(({ kind }) => [
|
|
173
|
+
"source-map-original-path",
|
|
174
|
+
"current-path-exact",
|
|
175
|
+
"current-path-suffix",
|
|
176
|
+
].includes(kind)));
|
|
177
|
+
const topScore = pathCandidates[0]?.score;
|
|
178
|
+
const topPathCandidates = topScore === undefined
|
|
179
|
+
? []
|
|
180
|
+
: pathCandidates.filter(({ score }) => score === topScore);
|
|
181
|
+
if (topPathCandidates.length === 1) {
|
|
182
|
+
const candidate = topPathCandidates[0];
|
|
183
|
+
return {
|
|
184
|
+
status: "unknown",
|
|
185
|
+
confidence: "unknown",
|
|
186
|
+
currentNodeIds: candidate === undefined ? [] : [candidate.current_node_id],
|
|
187
|
+
limitations: [
|
|
188
|
+
"Location evidence links the source to the current node, but comparable source digests are unavailable.",
|
|
189
|
+
],
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
if (topPathCandidates.length > 1)
|
|
193
|
+
return {
|
|
194
|
+
status: "split",
|
|
195
|
+
confidence: "medium",
|
|
196
|
+
currentNodeIds: topPathCandidates.map(({ current_node_id: nodeId }) => nodeId),
|
|
197
|
+
limitations: [
|
|
198
|
+
"Multiple equally scored current nodes retain the historical source path; this is a static split inference, not runtime proof.",
|
|
199
|
+
],
|
|
200
|
+
};
|
|
201
|
+
if (candidates.length > 0)
|
|
202
|
+
return {
|
|
203
|
+
status: "unknown",
|
|
204
|
+
confidence: "unknown",
|
|
205
|
+
currentNodeIds: [],
|
|
206
|
+
limitations: [
|
|
207
|
+
"Only weak basename or extension signals were available; no mapping was forced.",
|
|
208
|
+
],
|
|
209
|
+
};
|
|
210
|
+
return absenceComplete
|
|
211
|
+
? {
|
|
212
|
+
status: "removed",
|
|
213
|
+
confidence: "high",
|
|
214
|
+
currentNodeIds: [],
|
|
215
|
+
limitations: [
|
|
216
|
+
"No retained current node matched exact digest or path signals within complete static inventories.",
|
|
217
|
+
],
|
|
218
|
+
}
|
|
219
|
+
: {
|
|
220
|
+
status: "unknown",
|
|
221
|
+
confidence: "unknown",
|
|
222
|
+
currentNodeIds: [],
|
|
223
|
+
limitations: [
|
|
224
|
+
"No candidate was observed, but incomplete input or graph coverage prevents a removal claim.",
|
|
225
|
+
],
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
const markMergedMappings = (input, items) => {
|
|
229
|
+
const sourcesByNode = new Map();
|
|
230
|
+
for (const item of items) {
|
|
231
|
+
if (item.status !== "unchanged" || item.current_node_ids.length !== 1)
|
|
232
|
+
continue;
|
|
233
|
+
const nodeId = item.current_node_ids[0];
|
|
234
|
+
if (nodeId === undefined)
|
|
235
|
+
continue;
|
|
236
|
+
sourcesByNode.set(nodeId, [...(sourcesByNode.get(nodeId) ?? []), item]);
|
|
237
|
+
}
|
|
238
|
+
const mergedSources = new Set([...sourcesByNode.values()]
|
|
239
|
+
.filter((group) => group.length > 1)
|
|
240
|
+
.flatMap((group) => group.map(({ source_path: path }) => path)));
|
|
241
|
+
return items.map((item) => {
|
|
242
|
+
if (!mergedSources.has(item.source_path))
|
|
243
|
+
return item;
|
|
244
|
+
const { mapping_id: previousMappingId, ...base } = item;
|
|
245
|
+
void previousMappingId;
|
|
246
|
+
const semantic = {
|
|
247
|
+
...base,
|
|
248
|
+
status: "merged",
|
|
249
|
+
confidence: "exact",
|
|
250
|
+
limitations: uniqueSorted([
|
|
251
|
+
...item.limitations,
|
|
252
|
+
"Multiple historical source paths have exact digest identity with the same current graph node.",
|
|
253
|
+
]),
|
|
254
|
+
};
|
|
255
|
+
return { ...semantic, mapping_id: mappingId(input, semantic) };
|
|
256
|
+
});
|
|
257
|
+
};
|
|
258
|
+
const mappingId = (input, item) => `stbc_item_${digestCanonical({
|
|
259
|
+
reference_root_sha256: input.reference.root_sha256,
|
|
260
|
+
application_graph_id: input.application.graph.graph_id,
|
|
261
|
+
...item,
|
|
262
|
+
})}`;
|
|
263
|
+
const comparisonSummary = (items) => ({
|
|
264
|
+
unchanged: countStatus(items, "unchanged"),
|
|
265
|
+
modified: countStatus(items, "modified"),
|
|
266
|
+
removed: countStatus(items, "removed"),
|
|
267
|
+
split: countStatus(items, "split"),
|
|
268
|
+
merged: countStatus(items, "merged"),
|
|
269
|
+
duplicated: countStatus(items, "duplicated"),
|
|
270
|
+
unknown: countStatus(items, "unknown"),
|
|
271
|
+
});
|
|
272
|
+
const countStatus = (items, status) => items.filter((item) => item.status === status).length;
|
|
273
|
+
const coverageStatus = (factors) => {
|
|
274
|
+
if (factors.omittedSourceFiles > 0 ||
|
|
275
|
+
factors.omittedApplicationNodes > 0 ||
|
|
276
|
+
factors.omittedCandidateEvaluations > 0 ||
|
|
277
|
+
factors.omittedCandidateReferences > 0 ||
|
|
278
|
+
factors.pathIndexTruncated)
|
|
279
|
+
return "truncated";
|
|
280
|
+
return factors.input.reference.inventory_state === "complete" &&
|
|
281
|
+
factors.input.application.graph.coverage.status === "complete"
|
|
282
|
+
? "complete-within-inputs"
|
|
283
|
+
: "partial";
|
|
284
|
+
};
|
|
285
|
+
const comparisonLimitations = (factors) => uniqueSorted([
|
|
286
|
+
"Static digest and location signals do not establish runtime behavior, semantic equivalence, or absence of dynamically generated modules.",
|
|
287
|
+
...factors.input.reference.limitations,
|
|
288
|
+
...factors.input.application.graph.limitations,
|
|
289
|
+
...(factors.input.reference.inventory_state === "complete"
|
|
290
|
+
? []
|
|
291
|
+
: [
|
|
292
|
+
`Historical source inventory is ${factors.input.reference.inventory_state}; unmatched files remain unknown.`,
|
|
293
|
+
]),
|
|
294
|
+
...(factors.input.application.graph.coverage.status === "complete"
|
|
295
|
+
? []
|
|
296
|
+
: [
|
|
297
|
+
`Application graph coverage is ${factors.input.application.graph.coverage.status}; unmatched files remain unknown.`,
|
|
298
|
+
]),
|
|
299
|
+
...(factors.omittedSourceFiles > 0
|
|
300
|
+
? [
|
|
301
|
+
`${String(factors.omittedSourceFiles)} historical source files were omitted by max_source_files.`,
|
|
302
|
+
]
|
|
303
|
+
: []),
|
|
304
|
+
...(factors.omittedApplicationNodes > 0
|
|
305
|
+
? [
|
|
306
|
+
`${String(factors.omittedApplicationNodes)} source-bearing application nodes were omitted by max_application_nodes.`,
|
|
307
|
+
]
|
|
308
|
+
: []),
|
|
309
|
+
...(factors.omittedCandidateEvaluations > 0
|
|
310
|
+
? [
|
|
311
|
+
`${String(factors.omittedCandidateEvaluations)} candidate evaluations were omitted by max_candidate_evaluations.`,
|
|
312
|
+
]
|
|
313
|
+
: []),
|
|
314
|
+
...(factors.omittedCandidateReferences > 0
|
|
315
|
+
? [
|
|
316
|
+
`${String(factors.omittedCandidateReferences)} candidate references were omitted from item output.`,
|
|
317
|
+
]
|
|
318
|
+
: []),
|
|
319
|
+
...(factors.pathIndexTruncated
|
|
320
|
+
? [
|
|
321
|
+
"At least one current path exceeded the 128-segment suffix index bound.",
|
|
322
|
+
]
|
|
323
|
+
: []),
|
|
324
|
+
]);
|
|
325
|
+
const hasSignal = (candidate, kind) => candidate.signals.some((signal) => signal.kind === kind);
|
|
326
|
+
const compareCandidates = (left, right) => right.score - left.score ||
|
|
327
|
+
compareText(left.current_node_id, right.current_node_id);
|
|
328
|
+
const digestCanonical = (value) => {
|
|
329
|
+
const encoded = canonicalize(value);
|
|
330
|
+
if (encoded === undefined)
|
|
331
|
+
throw new TypeError("Source-to-bundle comparison could not canonicalize");
|
|
332
|
+
return createHash("sha256").update(encoded).digest("hex");
|
|
333
|
+
};
|
|
334
|
+
const uniqueSorted = (values) => [...new Set(values)].sort(compareText);
|
|
335
|
+
const compareText = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { evidenceSchema } from "./evidence.js";
|
|
3
|
+
import { JAVASCRIPT_APPLICATION_NODE_KINDS } from "./javascriptApplicationGraphSchemas.js";
|
|
4
|
+
import { historicalSourceGraphSchema } from "./referenceSourceGraph.js";
|
|
5
|
+
const digestSchema = z.string().regex(/^[a-f0-9]{64}$/u);
|
|
6
|
+
const evidenceIdSchema = z.string().regex(/^ev_[a-f0-9]{64}$/u);
|
|
7
|
+
const nodeIdSchema = z.string().regex(/^jag_node_[a-f0-9]{64}$/u);
|
|
8
|
+
const boundedTextSchema = z.string().min(1).max(4_096);
|
|
9
|
+
export const SOURCE_TO_BUNDLE_SIGNAL_WEIGHTS = {
|
|
10
|
+
"exact-source-digest": 100,
|
|
11
|
+
"source-map-original-path": 70,
|
|
12
|
+
"current-path-exact": 60,
|
|
13
|
+
"current-path-suffix": 40,
|
|
14
|
+
"basename-match": 20,
|
|
15
|
+
"language-extension": 5,
|
|
16
|
+
};
|
|
17
|
+
const sourceToBundleSignalKindSchema = z.enum([
|
|
18
|
+
"exact-source-digest",
|
|
19
|
+
"source-map-original-path",
|
|
20
|
+
"current-path-exact",
|
|
21
|
+
"current-path-suffix",
|
|
22
|
+
"basename-match",
|
|
23
|
+
"language-extension",
|
|
24
|
+
]);
|
|
25
|
+
const sourceToBundleLimitsSchema = z.strictObject({
|
|
26
|
+
max_source_files: z.number().int().min(1).max(100_000).default(10_000),
|
|
27
|
+
max_application_nodes: z.number().int().min(1).max(100_000).default(20_000),
|
|
28
|
+
max_candidate_nodes: z.number().int().min(1).max(1_000).default(100),
|
|
29
|
+
max_candidate_evaluations: z
|
|
30
|
+
.number()
|
|
31
|
+
.int()
|
|
32
|
+
.min(1)
|
|
33
|
+
.max(10_000_000)
|
|
34
|
+
.default(1_000_000),
|
|
35
|
+
});
|
|
36
|
+
/** Historical source inventory, authenticated application Evidence, and bounds. */
|
|
37
|
+
export const compareSourceToBundleInputSchema = z.strictObject({
|
|
38
|
+
reference: historicalSourceGraphSchema,
|
|
39
|
+
application: evidenceSchema,
|
|
40
|
+
limits: sourceToBundleLimitsSchema.default({
|
|
41
|
+
max_source_files: 10_000,
|
|
42
|
+
max_application_nodes: 20_000,
|
|
43
|
+
max_candidate_nodes: 100,
|
|
44
|
+
max_candidate_evaluations: 1_000_000,
|
|
45
|
+
}),
|
|
46
|
+
unknown_registry_approved: z.literal(true).optional(),
|
|
47
|
+
});
|
|
48
|
+
const sourceToBundleSignalSchema = z.strictObject({
|
|
49
|
+
kind: sourceToBundleSignalKindSchema,
|
|
50
|
+
weight: z.number().int().min(1).max(100),
|
|
51
|
+
source_value: boundedTextSchema,
|
|
52
|
+
current_values: z.array(boundedTextSchema).min(1).max(64),
|
|
53
|
+
});
|
|
54
|
+
const sourceToBundleCandidateSchema = z.strictObject({
|
|
55
|
+
current_node_id: nodeIdSchema,
|
|
56
|
+
current_node_kind: z.enum(JAVASCRIPT_APPLICATION_NODE_KINDS),
|
|
57
|
+
score: z.number().int().min(1).max(300),
|
|
58
|
+
confidence: z.enum(["exact", "high", "medium", "low"]),
|
|
59
|
+
signals: z.array(sourceToBundleSignalSchema).min(1).max(6),
|
|
60
|
+
});
|
|
61
|
+
const sourceToBundleItemSchema = z.strictObject({
|
|
62
|
+
mapping_id: z.string().regex(/^stbc_item_[a-f0-9]{64}$/u),
|
|
63
|
+
source_path: boundedTextSchema,
|
|
64
|
+
source_sha256: digestSchema.nullable(),
|
|
65
|
+
source_language: z.string().min(1).max(100).nullable(),
|
|
66
|
+
status: z.enum([
|
|
67
|
+
"unchanged",
|
|
68
|
+
"modified",
|
|
69
|
+
"removed",
|
|
70
|
+
"split",
|
|
71
|
+
"merged",
|
|
72
|
+
"duplicated",
|
|
73
|
+
"unknown",
|
|
74
|
+
]),
|
|
75
|
+
confidence: z.enum(["exact", "high", "medium", "unknown"]),
|
|
76
|
+
current_node_ids: z.array(nodeIdSchema).max(1_000),
|
|
77
|
+
candidates: z.array(sourceToBundleCandidateSchema).max(1_000),
|
|
78
|
+
omitted_candidates: z.number().int().min(0),
|
|
79
|
+
limitations: z.array(boundedTextSchema).max(100),
|
|
80
|
+
});
|
|
81
|
+
/** Deterministic, evidence-bearing historical-source to shipped-bundle comparison. */
|
|
82
|
+
export const sourceToBundleComparisonResultSchema = z.strictObject({
|
|
83
|
+
schema_version: z.literal(1),
|
|
84
|
+
comparison_id: z.string().regex(/^stbc_[a-f0-9]{64}$/u),
|
|
85
|
+
reference: z.strictObject({
|
|
86
|
+
root_sha256: digestSchema,
|
|
87
|
+
inventory_state: z.enum(["complete", "partial", "unknown"]),
|
|
88
|
+
}),
|
|
89
|
+
application: z.strictObject({
|
|
90
|
+
evidence_id: evidenceIdSchema,
|
|
91
|
+
graph_id: z.string().regex(/^jag_[a-f0-9]{64}$/u),
|
|
92
|
+
root_artifact_sha256: digestSchema,
|
|
93
|
+
}),
|
|
94
|
+
scoring: z.strictObject({
|
|
95
|
+
algorithm: z.literal("rea-source-to-bundle-signals/v1"),
|
|
96
|
+
minimum_candidate_score: z.literal(20),
|
|
97
|
+
weights: z.array(z.strictObject({
|
|
98
|
+
signal: sourceToBundleSignalKindSchema,
|
|
99
|
+
weight: z.number().int().min(1).max(100),
|
|
100
|
+
})),
|
|
101
|
+
}),
|
|
102
|
+
summary: z.strictObject({
|
|
103
|
+
unchanged: z.number().int().min(0),
|
|
104
|
+
modified: z.number().int().min(0),
|
|
105
|
+
removed: z.number().int().min(0),
|
|
106
|
+
split: z.number().int().min(0),
|
|
107
|
+
merged: z.number().int().min(0),
|
|
108
|
+
duplicated: z.number().int().min(0),
|
|
109
|
+
unknown: z.number().int().min(0),
|
|
110
|
+
}),
|
|
111
|
+
items: z.array(sourceToBundleItemSchema).max(100_000),
|
|
112
|
+
unmapped_current_node_ids: z.array(nodeIdSchema).max(100_000),
|
|
113
|
+
coverage: z.strictObject({
|
|
114
|
+
status: z.enum(["complete-within-inputs", "partial", "truncated"]),
|
|
115
|
+
reference_inventory_state: z.enum(["complete", "partial", "unknown"]),
|
|
116
|
+
application_graph_status: z.enum([
|
|
117
|
+
"complete",
|
|
118
|
+
"partial",
|
|
119
|
+
"unknown",
|
|
120
|
+
"unavailable",
|
|
121
|
+
]),
|
|
122
|
+
retained_source_files: z.number().int().min(0),
|
|
123
|
+
omitted_source_files: z.number().int().min(0),
|
|
124
|
+
retained_application_nodes: z.number().int().min(0),
|
|
125
|
+
omitted_application_nodes: z.number().int().min(0),
|
|
126
|
+
candidate_evaluations: z.number().int().min(0),
|
|
127
|
+
omitted_candidate_evaluations: z.number().int().min(0),
|
|
128
|
+
omitted_candidate_references: z.number().int().min(0),
|
|
129
|
+
omitted_unmapped_current_nodes: z.number().int().min(0),
|
|
130
|
+
}),
|
|
131
|
+
evidence_links: z.array(evidenceIdSchema).min(1).max(1),
|
|
132
|
+
limitations: z.array(boundedTextSchema).max(1_000),
|
|
133
|
+
});
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { posix } from "node:path";
|
|
2
|
+
import { SOURCE_TO_BUNDLE_SIGNAL_WEIGHTS, } from "./sourceToBundleComparisonSchemas.js";
|
|
3
|
+
const RELEVANT_NODE_KINDS = new Set([
|
|
4
|
+
"javascript-asset",
|
|
5
|
+
"javascript-module",
|
|
6
|
+
"source-module",
|
|
7
|
+
]);
|
|
8
|
+
const PATH_PROPERTIES = [
|
|
9
|
+
"path",
|
|
10
|
+
"source",
|
|
11
|
+
"original_source",
|
|
12
|
+
"module_path",
|
|
13
|
+
"resolved_path",
|
|
14
|
+
"declared_path",
|
|
15
|
+
];
|
|
16
|
+
const MAX_PATH_SUFFIXES = 128;
|
|
17
|
+
/** Keep source-bearing application nodes in stable identifier order. */
|
|
18
|
+
export const sourceBearingNodes = (nodes) => nodes
|
|
19
|
+
.filter(({ kind }) => RELEVANT_NODE_KINDS.has(kind))
|
|
20
|
+
.sort((left, right) => compareText(left.node_id, right.node_id));
|
|
21
|
+
/** Keep historical source files in stable path order. */
|
|
22
|
+
export const historicalSourceFiles = (graph) => graph.entries
|
|
23
|
+
.filter((entry) => entry.kind === "file" && entry.classifications.includes("source"))
|
|
24
|
+
.sort((left, right) => compareText(left.path, right.path));
|
|
25
|
+
/** Build bounded-key indices without assigning fuzzy matches. */
|
|
26
|
+
export const buildSourceToBundleCandidateIndex = (nodes) => {
|
|
27
|
+
const projections = nodes.map(projectCurrentNode);
|
|
28
|
+
const byDigest = new Map();
|
|
29
|
+
const byPathSuffix = new Map();
|
|
30
|
+
const byBasename = new Map();
|
|
31
|
+
let pathIndexTruncated = false;
|
|
32
|
+
for (const projection of projections) {
|
|
33
|
+
for (const digest of projection.digests)
|
|
34
|
+
addIndexValue(byDigest, digest, projection.node.node_id);
|
|
35
|
+
for (const path of projection.paths) {
|
|
36
|
+
const suffixes = pathSuffixes(path.value);
|
|
37
|
+
pathIndexTruncated ||= suffixes.truncated;
|
|
38
|
+
for (const suffix of suffixes.values)
|
|
39
|
+
addIndexValue(byPathSuffix, suffix, projection.node.node_id);
|
|
40
|
+
addIndexValue(byBasename, posix.basename(path.value), projection.node.node_id);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
nodes: new Map(projections.map((projection) => [projection.node.node_id, projection])),
|
|
45
|
+
byDigest,
|
|
46
|
+
byPathSuffix,
|
|
47
|
+
byBasename,
|
|
48
|
+
pathIndexTruncated,
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
/** Candidate identifiers ordered by strongest available signal, then ID. */
|
|
52
|
+
export const candidateIdsForSource = (source, index) => {
|
|
53
|
+
const groups = [
|
|
54
|
+
source.sha256 === null ? undefined : index.byDigest.get(source.sha256),
|
|
55
|
+
index.byPathSuffix.get(source.path),
|
|
56
|
+
index.byBasename.get(posix.basename(source.path)),
|
|
57
|
+
];
|
|
58
|
+
const seen = new Set();
|
|
59
|
+
const output = [];
|
|
60
|
+
for (const group of groups) {
|
|
61
|
+
if (group === undefined)
|
|
62
|
+
continue;
|
|
63
|
+
for (const nodeId of [...group].sort(compareText)) {
|
|
64
|
+
if (seen.has(nodeId))
|
|
65
|
+
continue;
|
|
66
|
+
seen.add(nodeId);
|
|
67
|
+
output.push(nodeId);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return output;
|
|
71
|
+
};
|
|
72
|
+
/** Score one indexed candidate from explicit, caller-visible signals. */
|
|
73
|
+
export const scoreSourceToBundleCandidate = (source, nodeId, index) => {
|
|
74
|
+
const projection = index.nodes.get(nodeId);
|
|
75
|
+
if (projection === undefined)
|
|
76
|
+
return null;
|
|
77
|
+
const signals = candidateSignals(source, projection);
|
|
78
|
+
const score = signals.reduce((total, signal) => total + signal.weight, 0);
|
|
79
|
+
if (score < 20)
|
|
80
|
+
return null;
|
|
81
|
+
return {
|
|
82
|
+
current_node_id: projection.node.node_id,
|
|
83
|
+
current_node_kind: projection.node.kind,
|
|
84
|
+
score,
|
|
85
|
+
confidence: candidateConfidence(signals),
|
|
86
|
+
signals,
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
const projectCurrentNode = (node) => {
|
|
90
|
+
const digests = new Set();
|
|
91
|
+
const paths = [];
|
|
92
|
+
if (node.identity.strategy === "content-digest")
|
|
93
|
+
digests.add(node.identity.sha256);
|
|
94
|
+
if (node.identity.strategy === "source-map-original") {
|
|
95
|
+
if (node.identity.source_sha256 !== null)
|
|
96
|
+
digests.add(node.identity.source_sha256);
|
|
97
|
+
addPath(paths, "source-map-original", node.identity.original_source);
|
|
98
|
+
}
|
|
99
|
+
if (node.identity.strategy === "canonical-path")
|
|
100
|
+
addPath(paths, "canonical-path", node.identity.path);
|
|
101
|
+
for (const observation of node.observations) {
|
|
102
|
+
const sourceDigest = observation.properties.source_sha256;
|
|
103
|
+
if (typeof sourceDigest === "string" && isDigest(sourceDigest))
|
|
104
|
+
digests.add(sourceDigest);
|
|
105
|
+
for (const key of PATH_PROPERTIES)
|
|
106
|
+
addJsonPath(paths, observation.properties[key]);
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
node,
|
|
110
|
+
digests,
|
|
111
|
+
paths: uniquePaths(paths),
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
const candidateSignals = (source, current) => {
|
|
115
|
+
const signals = [];
|
|
116
|
+
if (source.sha256 !== null && current.digests.has(source.sha256))
|
|
117
|
+
signals.push(signal("exact-source-digest", source.sha256, [source.sha256]));
|
|
118
|
+
const sourceMapPaths = matchingPaths(source.path, current.paths.filter(({ kind }) => kind === "source-map-original"));
|
|
119
|
+
if (sourceMapPaths.length > 0)
|
|
120
|
+
signals.push(signal("source-map-original-path", source.path, sourceMapPaths));
|
|
121
|
+
const otherPaths = current.paths.filter(({ kind }) => kind !== "source-map-original");
|
|
122
|
+
const exactPaths = otherPaths
|
|
123
|
+
.filter(({ value }) => value === source.path)
|
|
124
|
+
.map(({ value }) => value);
|
|
125
|
+
if (exactPaths.length > 0)
|
|
126
|
+
signals.push(signal("current-path-exact", source.path, exactPaths));
|
|
127
|
+
const suffixPaths = otherPaths
|
|
128
|
+
.filter(({ value }) => value !== source.path && value.endsWith(`/${source.path}`))
|
|
129
|
+
.map(({ value }) => value);
|
|
130
|
+
if (suffixPaths.length > 0)
|
|
131
|
+
signals.push(signal("current-path-suffix", source.path, suffixPaths));
|
|
132
|
+
const basename = posix.basename(source.path);
|
|
133
|
+
const basenamePaths = current.paths
|
|
134
|
+
.filter(({ value }) => posix.basename(value) === basename)
|
|
135
|
+
.map(({ value }) => value);
|
|
136
|
+
if (basenamePaths.length > 0)
|
|
137
|
+
signals.push(signal("basename-match", basename, basenamePaths));
|
|
138
|
+
const extension = posix.extname(source.path).toLowerCase();
|
|
139
|
+
const extensionPaths = current.paths
|
|
140
|
+
.filter(({ value }) => posix.extname(value).toLowerCase() === extension)
|
|
141
|
+
.map(({ value }) => value);
|
|
142
|
+
if (extension !== "" && extensionPaths.length > 0)
|
|
143
|
+
signals.push(signal("language-extension", extension, extensionPaths));
|
|
144
|
+
return signals;
|
|
145
|
+
};
|
|
146
|
+
const signal = (kind, sourceValue, currentValues) => ({
|
|
147
|
+
kind,
|
|
148
|
+
weight: SOURCE_TO_BUNDLE_SIGNAL_WEIGHTS[kind],
|
|
149
|
+
source_value: sourceValue,
|
|
150
|
+
current_values: [...new Set(currentValues)].sort(compareText).slice(0, 64),
|
|
151
|
+
});
|
|
152
|
+
const candidateConfidence = (signals) => {
|
|
153
|
+
const kinds = new Set(signals.map(({ kind }) => kind));
|
|
154
|
+
if (kinds.has("exact-source-digest"))
|
|
155
|
+
return "exact";
|
|
156
|
+
if (kinds.has("source-map-original-path") || kinds.has("current-path-exact"))
|
|
157
|
+
return "high";
|
|
158
|
+
return kinds.has("current-path-suffix") ? "medium" : "low";
|
|
159
|
+
};
|
|
160
|
+
const matchingPaths = (sourcePath, currentPaths) => currentPaths
|
|
161
|
+
.filter(({ value }) => value === sourcePath || value.endsWith(`/${sourcePath}`))
|
|
162
|
+
.map(({ value }) => value);
|
|
163
|
+
const addJsonPath = (paths, value) => {
|
|
164
|
+
if (typeof value === "string")
|
|
165
|
+
addPath(paths, "observation-path", value);
|
|
166
|
+
};
|
|
167
|
+
const addPath = (paths, kind, raw) => {
|
|
168
|
+
const value = normalizeCurrentPath(raw);
|
|
169
|
+
if (value !== null)
|
|
170
|
+
paths.push({ kind, value });
|
|
171
|
+
};
|
|
172
|
+
const normalizeCurrentPath = (raw) => {
|
|
173
|
+
const withoutQuery = raw.split(/[?#]/u, 1)[0] ?? "";
|
|
174
|
+
const withoutScheme = withoutQuery.replace(/^[a-z][a-z0-9+.-]*:\/\/+/iu, "");
|
|
175
|
+
const parts = withoutScheme
|
|
176
|
+
.replaceAll("\\", "/")
|
|
177
|
+
.split("/")
|
|
178
|
+
.filter((part) => part !== "" && part !== ".");
|
|
179
|
+
if (parts.length === 0 || parts.includes(".."))
|
|
180
|
+
return null;
|
|
181
|
+
return parts.join("/");
|
|
182
|
+
};
|
|
183
|
+
const uniquePaths = (paths) => [
|
|
184
|
+
...new Map(paths.map((path) => [`${path.kind}\0${path.value}`, path])).values(),
|
|
185
|
+
].sort((left, right) => compareText(`${left.kind}\0${left.value}`, `${right.kind}\0${right.value}`));
|
|
186
|
+
const pathSuffixes = (path) => {
|
|
187
|
+
const parts = path.split("/");
|
|
188
|
+
const start = Math.max(0, parts.length - MAX_PATH_SUFFIXES);
|
|
189
|
+
return {
|
|
190
|
+
values: parts
|
|
191
|
+
.slice(start)
|
|
192
|
+
.map((_, index) => parts.slice(start + index).join("/")),
|
|
193
|
+
truncated: start > 0,
|
|
194
|
+
};
|
|
195
|
+
};
|
|
196
|
+
const addIndexValue = (index, key, nodeId) => {
|
|
197
|
+
const values = index.get(key) ?? new Set();
|
|
198
|
+
values.add(nodeId);
|
|
199
|
+
index.set(key, values);
|
|
200
|
+
};
|
|
201
|
+
const isDigest = (value) => /^[a-f0-9]{64}$/u.test(value);
|
|
202
|
+
const compareText = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|