@veraxhq/verax 0.2.1 → 0.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 (152) hide show
  1. package/README.md +14 -18
  2. package/bin/verax.js +7 -0
  3. package/package.json +3 -3
  4. package/src/cli/commands/baseline.js +104 -0
  5. package/src/cli/commands/default.js +79 -25
  6. package/src/cli/commands/ga.js +243 -0
  7. package/src/cli/commands/gates.js +95 -0
  8. package/src/cli/commands/inspect.js +131 -2
  9. package/src/cli/commands/release-check.js +213 -0
  10. package/src/cli/commands/run.js +246 -35
  11. package/src/cli/commands/security-check.js +211 -0
  12. package/src/cli/commands/truth.js +114 -0
  13. package/src/cli/entry.js +304 -67
  14. package/src/cli/util/angular-component-extractor.js +179 -0
  15. package/src/cli/util/angular-navigation-detector.js +141 -0
  16. package/src/cli/util/angular-network-detector.js +161 -0
  17. package/src/cli/util/angular-state-detector.js +162 -0
  18. package/src/cli/util/ast-interactive-detector.js +546 -0
  19. package/src/cli/util/ast-network-detector.js +603 -0
  20. package/src/cli/util/ast-usestate-detector.js +602 -0
  21. package/src/cli/util/bootstrap-guard.js +86 -0
  22. package/src/cli/util/determinism-runner.js +123 -0
  23. package/src/cli/util/determinism-writer.js +129 -0
  24. package/src/cli/util/env-url.js +4 -0
  25. package/src/cli/util/expectation-extractor.js +369 -73
  26. package/src/cli/util/findings-writer.js +126 -16
  27. package/src/cli/util/learn-writer.js +3 -1
  28. package/src/cli/util/observe-writer.js +3 -1
  29. package/src/cli/util/paths.js +3 -12
  30. package/src/cli/util/project-discovery.js +3 -0
  31. package/src/cli/util/project-writer.js +3 -1
  32. package/src/cli/util/run-resolver.js +64 -0
  33. package/src/cli/util/source-requirement.js +55 -0
  34. package/src/cli/util/summary-writer.js +1 -0
  35. package/src/cli/util/svelte-navigation-detector.js +163 -0
  36. package/src/cli/util/svelte-network-detector.js +80 -0
  37. package/src/cli/util/svelte-sfc-extractor.js +147 -0
  38. package/src/cli/util/svelte-state-detector.js +243 -0
  39. package/src/cli/util/vue-navigation-detector.js +177 -0
  40. package/src/cli/util/vue-sfc-extractor.js +162 -0
  41. package/src/cli/util/vue-state-detector.js +215 -0
  42. package/src/verax/cli/finding-explainer.js +56 -3
  43. package/src/verax/core/artifacts/registry.js +154 -0
  44. package/src/verax/core/artifacts/verifier.js +980 -0
  45. package/src/verax/core/baseline/baseline.enforcer.js +137 -0
  46. package/src/verax/core/baseline/baseline.snapshot.js +231 -0
  47. package/src/verax/core/capabilities/gates.js +499 -0
  48. package/src/verax/core/capabilities/registry.js +475 -0
  49. package/src/verax/core/confidence/confidence-compute.js +137 -0
  50. package/src/verax/core/confidence/confidence-invariants.js +234 -0
  51. package/src/verax/core/confidence/confidence-report-writer.js +112 -0
  52. package/src/verax/core/confidence/confidence-weights.js +44 -0
  53. package/src/verax/core/confidence/confidence.defaults.js +65 -0
  54. package/src/verax/core/confidence/confidence.loader.js +79 -0
  55. package/src/verax/core/confidence/confidence.schema.js +94 -0
  56. package/src/verax/core/confidence-engine-refactor.js +484 -0
  57. package/src/verax/core/confidence-engine.js +486 -0
  58. package/src/verax/core/confidence-engine.js.backup +471 -0
  59. package/src/verax/core/contracts/index.js +29 -0
  60. package/src/verax/core/contracts/types.js +185 -0
  61. package/src/verax/core/contracts/validators.js +381 -0
  62. package/src/verax/core/decision-snapshot.js +30 -3
  63. package/src/verax/core/decisions/decision.trace.js +276 -0
  64. package/src/verax/core/determinism/contract-writer.js +89 -0
  65. package/src/verax/core/determinism/contract.js +139 -0
  66. package/src/verax/core/determinism/diff.js +364 -0
  67. package/src/verax/core/determinism/engine.js +221 -0
  68. package/src/verax/core/determinism/finding-identity.js +148 -0
  69. package/src/verax/core/determinism/normalize.js +438 -0
  70. package/src/verax/core/determinism/report-writer.js +92 -0
  71. package/src/verax/core/determinism/run-fingerprint.js +118 -0
  72. package/src/verax/core/dynamic-route-intelligence.js +528 -0
  73. package/src/verax/core/evidence/evidence-capture-service.js +307 -0
  74. package/src/verax/core/evidence/evidence-intent-ledger.js +165 -0
  75. package/src/verax/core/evidence-builder.js +487 -0
  76. package/src/verax/core/execution-mode-context.js +77 -0
  77. package/src/verax/core/execution-mode-detector.js +190 -0
  78. package/src/verax/core/failures/exit-codes.js +86 -0
  79. package/src/verax/core/failures/failure-summary.js +76 -0
  80. package/src/verax/core/failures/failure.factory.js +225 -0
  81. package/src/verax/core/failures/failure.ledger.js +132 -0
  82. package/src/verax/core/failures/failure.types.js +196 -0
  83. package/src/verax/core/failures/index.js +10 -0
  84. package/src/verax/core/ga/ga-report-writer.js +43 -0
  85. package/src/verax/core/ga/ga.artifact.js +49 -0
  86. package/src/verax/core/ga/ga.contract.js +434 -0
  87. package/src/verax/core/ga/ga.enforcer.js +86 -0
  88. package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
  89. package/src/verax/core/guardrails/policy.defaults.js +210 -0
  90. package/src/verax/core/guardrails/policy.loader.js +83 -0
  91. package/src/verax/core/guardrails/policy.schema.js +110 -0
  92. package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
  93. package/src/verax/core/guardrails-engine.js +505 -0
  94. package/src/verax/core/observe/run-timeline.js +316 -0
  95. package/src/verax/core/perf/perf.contract.js +186 -0
  96. package/src/verax/core/perf/perf.display.js +65 -0
  97. package/src/verax/core/perf/perf.enforcer.js +91 -0
  98. package/src/verax/core/perf/perf.monitor.js +209 -0
  99. package/src/verax/core/perf/perf.report.js +198 -0
  100. package/src/verax/core/pipeline-tracker.js +238 -0
  101. package/src/verax/core/product-definition.js +127 -0
  102. package/src/verax/core/release/provenance.builder.js +271 -0
  103. package/src/verax/core/release/release-report-writer.js +40 -0
  104. package/src/verax/core/release/release.enforcer.js +159 -0
  105. package/src/verax/core/release/reproducibility.check.js +221 -0
  106. package/src/verax/core/release/sbom.builder.js +283 -0
  107. package/src/verax/core/report/cross-index.js +192 -0
  108. package/src/verax/core/report/human-summary.js +222 -0
  109. package/src/verax/core/route-intelligence.js +419 -0
  110. package/src/verax/core/security/secrets.scan.js +326 -0
  111. package/src/verax/core/security/security-report.js +50 -0
  112. package/src/verax/core/security/security.enforcer.js +124 -0
  113. package/src/verax/core/security/supplychain.defaults.json +38 -0
  114. package/src/verax/core/security/supplychain.policy.js +326 -0
  115. package/src/verax/core/security/vuln.scan.js +265 -0
  116. package/src/verax/core/truth/truth.certificate.js +250 -0
  117. package/src/verax/core/ui-feedback-intelligence.js +515 -0
  118. package/src/verax/detect/confidence-engine.js +628 -40
  119. package/src/verax/detect/confidence-helper.js +33 -0
  120. package/src/verax/detect/detection-engine.js +18 -1
  121. package/src/verax/detect/dynamic-route-findings.js +335 -0
  122. package/src/verax/detect/expectation-chain-detector.js +417 -0
  123. package/src/verax/detect/expectation-model.js +3 -1
  124. package/src/verax/detect/findings-writer.js +141 -5
  125. package/src/verax/detect/index.js +229 -5
  126. package/src/verax/detect/journey-stall-detector.js +558 -0
  127. package/src/verax/detect/route-findings.js +218 -0
  128. package/src/verax/detect/ui-feedback-findings.js +207 -0
  129. package/src/verax/detect/verdict-engine.js +57 -3
  130. package/src/verax/detect/view-switch-correlator.js +242 -0
  131. package/src/verax/index.js +413 -45
  132. package/src/verax/learn/action-contract-extractor.js +682 -64
  133. package/src/verax/learn/route-validator.js +4 -1
  134. package/src/verax/observe/index.js +88 -843
  135. package/src/verax/observe/interaction-runner.js +25 -8
  136. package/src/verax/observe/observe-context.js +205 -0
  137. package/src/verax/observe/observe-helpers.js +191 -0
  138. package/src/verax/observe/observe-runner.js +226 -0
  139. package/src/verax/observe/observers/budget-observer.js +185 -0
  140. package/src/verax/observe/observers/console-observer.js +102 -0
  141. package/src/verax/observe/observers/coverage-observer.js +107 -0
  142. package/src/verax/observe/observers/interaction-observer.js +471 -0
  143. package/src/verax/observe/observers/navigation-observer.js +132 -0
  144. package/src/verax/observe/observers/network-observer.js +87 -0
  145. package/src/verax/observe/observers/safety-observer.js +82 -0
  146. package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
  147. package/src/verax/observe/ui-feedback-detector.js +742 -0
  148. package/src/verax/observe/ui-signal-sensor.js +148 -2
  149. package/src/verax/scan-summary-writer.js +42 -8
  150. package/src/verax/shared/artifact-manager.js +8 -5
  151. package/src/verax/shared/css-spinner-rules.js +204 -0
  152. package/src/verax/shared/view-switch-rules.js +208 -0
@@ -0,0 +1,33 @@
1
+ /**
2
+ * PHASE 15 — Confidence Helper
3
+ *
4
+ * Helper function to add unified confidence to findings
5
+ */
6
+
7
+ import { computeConfidenceForFinding } from '../core/confidence-engine.js';
8
+
9
+ /**
10
+ * PHASE 15: Add unified confidence to a finding
11
+ *
12
+ * @param {Object} finding - Finding object
13
+ * @param {Object} params - Confidence computation parameters
14
+ * @returns {Object} Finding with unified confidence fields
15
+ */
16
+ export function addUnifiedConfidence(finding, params) {
17
+ const unifiedConfidence = computeConfidenceForFinding({
18
+ findingType: finding.type || 'unknown',
19
+ expectation: params.expectation || null,
20
+ sensors: params.sensors || {},
21
+ comparisons: params.comparisons || {},
22
+ evidence: params.evidence || {},
23
+ });
24
+
25
+ // Add unified confidence fields (additive only)
26
+ return {
27
+ ...finding,
28
+ confidence: unifiedConfidence.score, // PHASE 15: Normalized 0..1
29
+ confidenceLevel: unifiedConfidence.level, // PHASE 15: HIGH/MEDIUM/LOW/UNPROVEN
30
+ confidenceReasons: unifiedConfidence.reasons, // PHASE 15: Stable reason codes
31
+ };
32
+ }
33
+
@@ -2,11 +2,19 @@
2
2
  * Detection Engine: Core of VERAX
3
3
  * Compares learn.json and observe.json to produce evidence-backed findings
4
4
  * with deterministic classification and confidence calculation
5
+ *
6
+ * PHASE 11: EXPECTATION CONTINUITY
7
+ * - Detects journey-level stalls (individual steps OK, overall journey stalls)
8
+ * - Tracks interaction sequences and infers expected progression signals
9
+ * - Emits "journey-stall-silent-failure" findings with high-confidence context
5
10
  */
6
11
 
12
+ import JourneyStallDetector from './journey-stall-detector.js';
13
+
7
14
  class DetectionEngine {
8
15
  constructor(options = {}) {
9
16
  this.options = options;
17
+ this.journeyStallDetector = new JourneyStallDetector(options.journeyStall || {});
10
18
  }
11
19
 
12
20
  /**
@@ -22,6 +30,7 @@ class DetectionEngine {
22
30
 
23
31
  const expectations = learnData.expectations || [];
24
32
  const observations = observeData.observations || [];
33
+ const traces = observeData.traces || [];
25
34
 
26
35
  // Index observations for fast lookup
27
36
  const observationMap = this._indexObservations(observations);
@@ -31,6 +40,10 @@ class DetectionEngine {
31
40
  return this._classifyExpectation(expectation, observationMap, observations);
32
41
  });
33
42
 
43
+ // PHASE 11: Detect journey-level stalls
44
+ const journeyStallFindings = this.journeyStallDetector.detectStalls(traces);
45
+ findings.push(...journeyStallFindings);
46
+
34
47
  // Calculate stats
35
48
  const stats = this._calculateStats(findings);
36
49
 
@@ -38,7 +51,11 @@ class DetectionEngine {
38
51
  findings,
39
52
  stats,
40
53
  detectedAt: new Date().toISOString(),
41
- version: '1.0.0'
54
+ version: '1.1.0',
55
+ phaseFeatures: {
56
+ expectationContinuity: true,
57
+ journeyStallDetection: true
58
+ }
42
59
  };
43
60
  }
44
61
 
@@ -0,0 +1,335 @@
1
+ /**
2
+ * PHASE 14 — Dynamic Route Findings Detector
3
+ *
4
+ * Detects dynamic route-related findings with proper verifiability classification
5
+ * and intentional skips for unverifiable routes.
6
+ */
7
+
8
+ import {
9
+ classifyDynamicRoute,
10
+ correlateDynamicRouteNavigation,
11
+ buildDynamicRouteEvidence,
12
+ shouldSkipDynamicRoute,
13
+ DYNAMIC_ROUTE_VERIFIABILITY,
14
+ ROUTE_VERDICT,
15
+ } from '../core/dynamic-route-intelligence.js';
16
+ import { buildRouteModels } from '../core/route-intelligence.js';
17
+ import { computeConfidence } from './confidence-engine.js';
18
+ import { computeConfidenceForFinding } from '../core/confidence-engine.js';
19
+ import { buildAndEnforceEvidencePackage } from '../core/evidence-builder.js';
20
+ import { applyGuardrails } from '../core/guardrails-engine.js';
21
+
22
+ /**
23
+ * PHASE 14: Detect dynamic route-related findings
24
+ *
25
+ * @param {Array} traces - Interaction traces
26
+ * @param {Object} manifest - Project manifest with routes and expectations
27
+ * @param {Array} findings - Findings array to append to
28
+ * @returns {Object} { findings: Array, skips: Array }
29
+ */
30
+ export function detectDynamicRouteFindings(traces, manifest, findings) {
31
+ const dynamicRouteFindings = [];
32
+ const skips = [];
33
+
34
+ // Build route models from manifest routes
35
+ const routeModels = buildRouteModels(manifest.routes || []);
36
+
37
+ // Process each trace
38
+ for (const trace of traces) {
39
+ const interaction = trace.interaction || {};
40
+
41
+ // Find navigation expectations for this interaction
42
+ const navigationExpectations = findNavigationExpectations(manifest, interaction, trace);
43
+
44
+ for (const expectation of navigationExpectations) {
45
+ const navigationTarget = expectation.targetPath || expectation.expectedTarget || '';
46
+
47
+ if (!navigationTarget) continue;
48
+
49
+ // Find matching route model
50
+ const matchingRoute = findMatchingRoute(navigationTarget, routeModels);
51
+
52
+ if (!matchingRoute) {
53
+ // No route match - handled by regular route findings
54
+ continue;
55
+ }
56
+
57
+ // Check if route is dynamic
58
+ const classification = classifyDynamicRoute(matchingRoute, trace);
59
+
60
+ // If route is unverifiable, add to skips
61
+ if (classification.verifiability === DYNAMIC_ROUTE_VERIFIABILITY.UNVERIFIABLE_DYNAMIC) {
62
+ const skipDecision = shouldSkipDynamicRoute(matchingRoute, trace);
63
+
64
+ skips.push({
65
+ type: 'dynamic_route_unverifiable',
66
+ interaction: {
67
+ type: interaction.type,
68
+ selector: interaction.selector,
69
+ label: interaction.label,
70
+ },
71
+ route: {
72
+ path: matchingRoute.path,
73
+ originalPattern: matchingRoute.originalPattern,
74
+ sourceRef: matchingRoute.sourceRef,
75
+ },
76
+ reason: skipDecision.reason,
77
+ confidence: skipDecision.confidence,
78
+ expectation: {
79
+ target: navigationTarget,
80
+ source: expectation.source,
81
+ },
82
+ });
83
+ continue;
84
+ }
85
+
86
+ // Correlate navigation with dynamic route
87
+ const correlation = correlateDynamicRouteNavigation(expectation, matchingRoute, trace);
88
+
89
+ // If correlation indicates skip, add to skips
90
+ if (correlation.skip) {
91
+ skips.push({
92
+ type: 'dynamic_route_skip',
93
+ interaction: {
94
+ type: interaction.type,
95
+ selector: interaction.selector,
96
+ label: interaction.label,
97
+ },
98
+ route: {
99
+ path: matchingRoute.path,
100
+ originalPattern: matchingRoute.originalPattern,
101
+ sourceRef: matchingRoute.sourceRef,
102
+ },
103
+ reason: correlation.skipReason,
104
+ confidence: correlation.confidence,
105
+ expectation: {
106
+ target: navigationTarget,
107
+ source: expectation.source,
108
+ },
109
+ });
110
+ continue;
111
+ }
112
+
113
+ // Generate finding if verdict indicates failure
114
+ if (correlation.verdict === ROUTE_VERDICT.SILENT_FAILURE ||
115
+ correlation.verdict === ROUTE_VERDICT.ROUTE_MISMATCH ||
116
+ (correlation.verdict === ROUTE_VERDICT.AMBIGUOUS && correlation.confidence >= 0.7)) {
117
+
118
+ // Build evidence
119
+ const evidence = buildDynamicRouteEvidence(expectation, matchingRoute, correlation, trace);
120
+
121
+ // PHASE 14: Evidence Law - require sufficient evidence for CONFIRMED
122
+ const hasSufficientEvidence = evidence.beforeAfter.beforeUrl &&
123
+ evidence.beforeAfter.afterUrl &&
124
+ (evidence.signals.urlChanged ||
125
+ evidence.signals.routeMatched ||
126
+ evidence.signals.uiFeedback !== 'FEEDBACK_MISSING' ||
127
+ evidence.signals.domChanged);
128
+
129
+ // PHASE 15: Compute unified confidence
130
+ const unifiedConfidence = computeConfidenceForFinding({
131
+ findingType: findingType,
132
+ expectation,
133
+ sensors: trace.sensors || {},
134
+ comparisons: {},
135
+ evidence,
136
+ });
137
+
138
+ // Legacy confidence for backward compatibility
139
+ const confidence = computeConfidence({
140
+ findingType: 'dynamic_route_silent_failure',
141
+ expectation,
142
+ sensors: trace.sensors || {},
143
+ comparisons: {},
144
+ attemptMeta: {},
145
+ });
146
+
147
+ // Determine severity based on evidence and verdict
148
+ let severity = 'SUSPECTED';
149
+ if (hasSufficientEvidence && correlation.verdict === ROUTE_VERDICT.SILENT_FAILURE && unifiedConfidence.score >= 0.8) {
150
+ severity = 'CONFIRMED';
151
+ } else if (correlation.verdict === ROUTE_VERDICT.ROUTE_MISMATCH && hasSufficientEvidence) {
152
+ severity = 'CONFIRMED';
153
+ }
154
+
155
+ // Determine finding type
156
+ let findingType = 'dynamic_route_silent_failure';
157
+ if (correlation.verdict === ROUTE_VERDICT.ROUTE_MISMATCH) {
158
+ findingType = 'dynamic_route_mismatch';
159
+ } else if (correlation.verdict === ROUTE_VERDICT.AMBIGUOUS) {
160
+ findingType = 'dynamic_route_ambiguous';
161
+ }
162
+
163
+ const finding = {
164
+ type: findingType,
165
+ severity,
166
+ confidence: unifiedConfidence.score, // PHASE 15: Use unified confidence score (0..1)
167
+ confidenceLevel: unifiedConfidence.level, // PHASE 15: Add confidence level
168
+ confidenceReasons: unifiedConfidence.reasons, // PHASE 15: Add stable reason codes
169
+ interaction: {
170
+ type: interaction.type,
171
+ selector: interaction.selector,
172
+ label: interaction.label,
173
+ },
174
+ reason: correlation.reason || 'Dynamic route navigation outcome unclear',
175
+ evidence,
176
+ source: {
177
+ file: expectation.source?.file || null,
178
+ line: expectation.source?.line || null,
179
+ column: expectation.source?.column || null,
180
+ context: expectation.source?.context || null,
181
+ astSource: expectation.source?.astSource || null,
182
+ },
183
+ route: correlation.route,
184
+ expectation,
185
+ classification: classification,
186
+ classificationReason: classificationReason,
187
+ };
188
+
189
+ // PHASE 16: Build and enforce evidence package
190
+ const findingWithEvidence = buildAndEnforceEvidencePackage(finding, {
191
+ expectation,
192
+ trace,
193
+ evidence,
194
+ confidence: unifiedConfidence,
195
+ });
196
+
197
+ // PHASE 17: Apply guardrails (AFTER evidence builder)
198
+ const context = {
199
+ evidencePackage: findingWithEvidence.evidencePackage,
200
+ signals: findingWithEvidence.evidencePackage?.signals || evidence.signals || {},
201
+ confidenceReasons: unifiedConfidence.reasons || [],
202
+ promiseType: expectation?.type || null,
203
+ };
204
+ const { finding: findingWithGuardrails } = applyGuardrails(findingWithEvidence, context);
205
+
206
+ dynamicRouteFindings.push(findingWithGuardrails);
207
+ }
208
+ }
209
+ }
210
+
211
+ return {
212
+ findings: dynamicRouteFindings,
213
+ skips: skips,
214
+ };
215
+ }
216
+
217
+ /**
218
+ * Find navigation expectations matching the interaction
219
+ */
220
+ function findNavigationExpectations(manifest, interaction, trace) {
221
+ const expectations = [];
222
+
223
+ // Check static expectations
224
+ if (manifest.staticExpectations) {
225
+ const beforeUrl = trace.before?.url || trace.sensors?.navigation?.beforeUrl || '';
226
+ const beforePath = extractPathFromUrl(beforeUrl);
227
+
228
+ if (beforePath) {
229
+ const normalizedBefore = beforePath.replace(/\/$/, '') || '/';
230
+
231
+ for (const expectation of manifest.staticExpectations) {
232
+ if (expectation.type !== 'navigation' && expectation.type !== 'spa_navigation') {
233
+ continue;
234
+ }
235
+
236
+ const normalizedFrom = (expectation.fromPath || '').replace(/\/$/, '') || '/';
237
+ if (normalizedFrom === normalizedBefore) {
238
+ const selectorHint = expectation.selectorHint || '';
239
+ const interactionSelector = interaction.selector || '';
240
+
241
+ if (!selectorHint || !interactionSelector ||
242
+ selectorHint === interactionSelector ||
243
+ selectorHint.includes(interactionSelector) ||
244
+ interactionSelector.includes(selectorHint)) {
245
+ expectations.push(expectation);
246
+ }
247
+ }
248
+ }
249
+ }
250
+ }
251
+
252
+ // Check expectations from intel (AST-based)
253
+ if (manifest.expectations) {
254
+ for (const expectation of manifest.expectations) {
255
+ if (expectation.type === 'navigation' || expectation.type === 'spa_navigation') {
256
+ const selectorHint = expectation.selectorHint || '';
257
+ const interactionSelector = interaction.selector || '';
258
+ const interactionLabel = (interaction.label || '').toLowerCase();
259
+ const expectationLabel = (expectation.promise?.value || '').toLowerCase();
260
+
261
+ if (selectorHint === interactionSelector ||
262
+ (expectationLabel && interactionLabel && expectationLabel.includes(interactionLabel))) {
263
+ expectations.push(expectation);
264
+ }
265
+ }
266
+ }
267
+ }
268
+
269
+ return expectations;
270
+ }
271
+
272
+ /**
273
+ * Find matching route model for navigation target
274
+ */
275
+ function findMatchingRoute(navigationTarget, routeModels) {
276
+ // Try exact match first
277
+ let matchedRoute = routeModels.find(r => r.path === navigationTarget);
278
+
279
+ if (matchedRoute) {
280
+ return matchedRoute;
281
+ }
282
+
283
+ // Try pattern match for dynamic routes
284
+ for (const route of routeModels) {
285
+ if (route.isDynamic && route.originalPattern) {
286
+ if (matchDynamicPattern(navigationTarget, route.originalPattern)) {
287
+ return route;
288
+ }
289
+ }
290
+ }
291
+
292
+ return null;
293
+ }
294
+
295
+ /**
296
+ * Match dynamic pattern against actual path
297
+ */
298
+ function matchDynamicPattern(actualPath, pattern) {
299
+ if (!actualPath || !pattern) return false;
300
+
301
+ // Convert pattern to regex
302
+ let regexPattern = pattern;
303
+
304
+ // Replace :param with (\w+)
305
+ regexPattern = regexPattern.replace(/:(\w+)/g, '(\\w+)');
306
+
307
+ // Replace [param] with (\w+)
308
+ regexPattern = regexPattern.replace(/\[(\w+)\]/g, '(\\w+)');
309
+
310
+ // Escape other special characters
311
+ regexPattern = regexPattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
312
+
313
+ // Restore the capture groups
314
+ regexPattern = regexPattern.replace(/\\\(\\\\w\+\\\)/g, '(\\w+)');
315
+
316
+ const regex = new RegExp(`^${regexPattern}$`);
317
+ return regex.test(actualPath);
318
+ }
319
+
320
+ /**
321
+ * Extract path from URL
322
+ */
323
+ function extractPathFromUrl(url) {
324
+ if (!url || typeof url !== 'string') return '';
325
+
326
+ try {
327
+ const urlObj = new URL(url);
328
+ return urlObj.pathname;
329
+ } catch {
330
+ // Relative URL
331
+ const pathMatch = url.match(/^([^?#]+)/);
332
+ return pathMatch ? pathMatch[1] : url;
333
+ }
334
+ }
335
+