@veraxhq/verax 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. package/README.md +10 -6
  2. package/bin/verax.js +11 -11
  3. package/package.json +29 -8
  4. package/src/cli/commands/baseline.js +103 -0
  5. package/src/cli/commands/default.js +51 -6
  6. package/src/cli/commands/doctor.js +29 -0
  7. package/src/cli/commands/ga.js +246 -0
  8. package/src/cli/commands/gates.js +95 -0
  9. package/src/cli/commands/inspect.js +4 -2
  10. package/src/cli/commands/release-check.js +215 -0
  11. package/src/cli/commands/run.js +45 -6
  12. package/src/cli/commands/security-check.js +212 -0
  13. package/src/cli/commands/truth.js +113 -0
  14. package/src/cli/entry.js +30 -20
  15. package/src/cli/util/angular-component-extractor.js +179 -0
  16. package/src/cli/util/angular-navigation-detector.js +141 -0
  17. package/src/cli/util/angular-network-detector.js +161 -0
  18. package/src/cli/util/angular-state-detector.js +162 -0
  19. package/src/cli/util/ast-interactive-detector.js +544 -0
  20. package/src/cli/util/ast-network-detector.js +603 -0
  21. package/src/cli/util/ast-promise-extractor.js +581 -0
  22. package/src/cli/util/ast-usestate-detector.js +602 -0
  23. package/src/cli/util/atomic-write.js +12 -1
  24. package/src/cli/util/bootstrap-guard.js +86 -0
  25. package/src/cli/util/console-reporter.js +72 -0
  26. package/src/cli/util/detection-engine.js +105 -41
  27. package/src/cli/util/determinism-runner.js +124 -0
  28. package/src/cli/util/determinism-writer.js +129 -0
  29. package/src/cli/util/digest-engine.js +359 -0
  30. package/src/cli/util/dom-diff.js +226 -0
  31. package/src/cli/util/evidence-engine.js +287 -0
  32. package/src/cli/util/expectation-extractor.js +151 -5
  33. package/src/cli/util/findings-writer.js +3 -0
  34. package/src/cli/util/framework-detector.js +572 -0
  35. package/src/cli/util/idgen.js +1 -1
  36. package/src/cli/util/interaction-planner.js +529 -0
  37. package/src/cli/util/learn-writer.js +2 -0
  38. package/src/cli/util/ledger-writer.js +110 -0
  39. package/src/cli/util/monorepo-resolver.js +162 -0
  40. package/src/cli/util/observation-engine.js +127 -278
  41. package/src/cli/util/observe-writer.js +2 -0
  42. package/src/cli/util/project-discovery.js +284 -0
  43. package/src/cli/util/project-writer.js +2 -0
  44. package/src/cli/util/run-id.js +23 -27
  45. package/src/cli/util/run-resolver.js +64 -0
  46. package/src/cli/util/run-result.js +778 -0
  47. package/src/cli/util/selector-resolver.js +235 -0
  48. package/src/cli/util/source-requirement.js +55 -0
  49. package/src/cli/util/summary-writer.js +2 -0
  50. package/src/cli/util/svelte-navigation-detector.js +163 -0
  51. package/src/cli/util/svelte-network-detector.js +80 -0
  52. package/src/cli/util/svelte-sfc-extractor.js +146 -0
  53. package/src/cli/util/svelte-state-detector.js +242 -0
  54. package/src/cli/util/trust-activation-integration.js +496 -0
  55. package/src/cli/util/trust-activation-wrapper.js +85 -0
  56. package/src/cli/util/trust-integration-hooks.js +164 -0
  57. package/src/cli/util/types.js +153 -0
  58. package/src/cli/util/url-validation.js +40 -0
  59. package/src/cli/util/vue-navigation-detector.js +178 -0
  60. package/src/cli/util/vue-sfc-extractor.js +161 -0
  61. package/src/cli/util/vue-state-detector.js +215 -0
  62. package/src/types/fs-augment.d.ts +23 -0
  63. package/src/types/global.d.ts +137 -0
  64. package/src/types/internal-types.d.ts +35 -0
  65. package/src/verax/cli/init.js +4 -18
  66. package/src/verax/core/action-classifier.js +4 -3
  67. package/src/verax/core/artifacts/registry.js +139 -0
  68. package/src/verax/core/artifacts/verifier.js +990 -0
  69. package/src/verax/core/baseline/baseline.enforcer.js +137 -0
  70. package/src/verax/core/baseline/baseline.snapshot.js +233 -0
  71. package/src/verax/core/capabilities/gates.js +505 -0
  72. package/src/verax/core/capabilities/registry.js +475 -0
  73. package/src/verax/core/confidence/confidence-compute.js +144 -0
  74. package/src/verax/core/confidence/confidence-invariants.js +234 -0
  75. package/src/verax/core/confidence/confidence-report-writer.js +112 -0
  76. package/src/verax/core/confidence/confidence-weights.js +44 -0
  77. package/src/verax/core/confidence/confidence.defaults.js +65 -0
  78. package/src/verax/core/confidence/confidence.loader.js +80 -0
  79. package/src/verax/core/confidence/confidence.schema.js +94 -0
  80. package/src/verax/core/confidence-engine-refactor.js +489 -0
  81. package/src/verax/core/confidence-engine.js +625 -0
  82. package/src/verax/core/contracts/index.js +29 -0
  83. package/src/verax/core/contracts/types.js +186 -0
  84. package/src/verax/core/contracts/validators.js +456 -0
  85. package/src/verax/core/decisions/decision.trace.js +278 -0
  86. package/src/verax/core/determinism/contract-writer.js +89 -0
  87. package/src/verax/core/determinism/contract.js +139 -0
  88. package/src/verax/core/determinism/diff.js +405 -0
  89. package/src/verax/core/determinism/engine.js +222 -0
  90. package/src/verax/core/determinism/finding-identity.js +149 -0
  91. package/src/verax/core/determinism/normalize.js +466 -0
  92. package/src/verax/core/determinism/report-writer.js +93 -0
  93. package/src/verax/core/determinism/run-fingerprint.js +123 -0
  94. package/src/verax/core/dynamic-route-intelligence.js +529 -0
  95. package/src/verax/core/evidence/evidence-capture-service.js +308 -0
  96. package/src/verax/core/evidence/evidence-intent-ledger.js +166 -0
  97. package/src/verax/core/evidence-builder.js +487 -0
  98. package/src/verax/core/execution-mode-context.js +77 -0
  99. package/src/verax/core/execution-mode-detector.js +192 -0
  100. package/src/verax/core/failures/exit-codes.js +88 -0
  101. package/src/verax/core/failures/failure-summary.js +76 -0
  102. package/src/verax/core/failures/failure.factory.js +225 -0
  103. package/src/verax/core/failures/failure.ledger.js +133 -0
  104. package/src/verax/core/failures/failure.types.js +196 -0
  105. package/src/verax/core/failures/index.js +10 -0
  106. package/src/verax/core/ga/ga-report-writer.js +43 -0
  107. package/src/verax/core/ga/ga.artifact.js +49 -0
  108. package/src/verax/core/ga/ga.contract.js +435 -0
  109. package/src/verax/core/ga/ga.enforcer.js +87 -0
  110. package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
  111. package/src/verax/core/guardrails/policy.defaults.js +210 -0
  112. package/src/verax/core/guardrails/policy.loader.js +84 -0
  113. package/src/verax/core/guardrails/policy.schema.js +110 -0
  114. package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
  115. package/src/verax/core/guardrails-engine.js +505 -0
  116. package/src/verax/core/incremental-store.js +1 -0
  117. package/src/verax/core/integrity/budget.js +138 -0
  118. package/src/verax/core/integrity/determinism.js +342 -0
  119. package/src/verax/core/integrity/integrity.js +208 -0
  120. package/src/verax/core/integrity/poisoning.js +108 -0
  121. package/src/verax/core/integrity/transaction.js +140 -0
  122. package/src/verax/core/observe/run-timeline.js +318 -0
  123. package/src/verax/core/perf/perf.contract.js +186 -0
  124. package/src/verax/core/perf/perf.display.js +65 -0
  125. package/src/verax/core/perf/perf.enforcer.js +91 -0
  126. package/src/verax/core/perf/perf.monitor.js +209 -0
  127. package/src/verax/core/perf/perf.report.js +200 -0
  128. package/src/verax/core/pipeline-tracker.js +243 -0
  129. package/src/verax/core/product-definition.js +127 -0
  130. package/src/verax/core/release/provenance.builder.js +130 -0
  131. package/src/verax/core/release/release-report-writer.js +40 -0
  132. package/src/verax/core/release/release.enforcer.js +164 -0
  133. package/src/verax/core/release/reproducibility.check.js +222 -0
  134. package/src/verax/core/release/sbom.builder.js +292 -0
  135. package/src/verax/core/replay-validator.js +2 -0
  136. package/src/verax/core/replay.js +4 -0
  137. package/src/verax/core/report/cross-index.js +195 -0
  138. package/src/verax/core/report/human-summary.js +362 -0
  139. package/src/verax/core/route-intelligence.js +420 -0
  140. package/src/verax/core/run-id.js +6 -3
  141. package/src/verax/core/run-manifest.js +4 -3
  142. package/src/verax/core/security/secrets.scan.js +329 -0
  143. package/src/verax/core/security/security-report.js +50 -0
  144. package/src/verax/core/security/security.enforcer.js +128 -0
  145. package/src/verax/core/security/supplychain.defaults.json +38 -0
  146. package/src/verax/core/security/supplychain.policy.js +334 -0
  147. package/src/verax/core/security/vuln.scan.js +265 -0
  148. package/src/verax/core/truth/truth.certificate.js +252 -0
  149. package/src/verax/core/ui-feedback-intelligence.js +481 -0
  150. package/src/verax/detect/conditional-ui-silent-failure.js +84 -0
  151. package/src/verax/detect/confidence-engine.js +62 -34
  152. package/src/verax/detect/confidence-helper.js +34 -0
  153. package/src/verax/detect/dynamic-route-findings.js +338 -0
  154. package/src/verax/detect/expectation-chain-detector.js +417 -0
  155. package/src/verax/detect/expectation-model.js +2 -2
  156. package/src/verax/detect/failure-cause-inference.js +293 -0
  157. package/src/verax/detect/findings-writer.js +131 -35
  158. package/src/verax/detect/flow-detector.js +2 -2
  159. package/src/verax/detect/form-silent-failure.js +98 -0
  160. package/src/verax/detect/index.js +46 -5
  161. package/src/verax/detect/invariants-enforcer.js +147 -0
  162. package/src/verax/detect/journey-stall-detector.js +558 -0
  163. package/src/verax/detect/navigation-silent-failure.js +82 -0
  164. package/src/verax/detect/problem-aggregator.js +361 -0
  165. package/src/verax/detect/route-findings.js +219 -0
  166. package/src/verax/detect/summary-writer.js +477 -0
  167. package/src/verax/detect/test-failure-cause-inference.js +314 -0
  168. package/src/verax/detect/ui-feedback-findings.js +207 -0
  169. package/src/verax/detect/view-switch-correlator.js +242 -0
  170. package/src/verax/flow/flow-engine.js +2 -1
  171. package/src/verax/flow/flow-spec.js +0 -6
  172. package/src/verax/index.js +4 -0
  173. package/src/verax/intel/ts-program.js +1 -0
  174. package/src/verax/intel/vue-navigation-extractor.js +3 -0
  175. package/src/verax/learn/action-contract-extractor.js +3 -0
  176. package/src/verax/learn/ast-contract-extractor.js +1 -1
  177. package/src/verax/learn/flow-extractor.js +1 -0
  178. package/src/verax/learn/project-detector.js +5 -0
  179. package/src/verax/learn/react-router-extractor.js +2 -0
  180. package/src/verax/learn/source-instrumenter.js +1 -0
  181. package/src/verax/learn/state-extractor.js +2 -1
  182. package/src/verax/learn/static-extractor.js +1 -0
  183. package/src/verax/observe/coverage-gaps.js +132 -0
  184. package/src/verax/observe/expectation-handler.js +126 -0
  185. package/src/verax/observe/incremental-skip.js +46 -0
  186. package/src/verax/observe/index.js +51 -155
  187. package/src/verax/observe/interaction-executor.js +192 -0
  188. package/src/verax/observe/interaction-runner.js +782 -513
  189. package/src/verax/observe/network-firewall.js +86 -0
  190. package/src/verax/observe/observation-builder.js +169 -0
  191. package/src/verax/observe/observe-context.js +205 -0
  192. package/src/verax/observe/observe-helpers.js +192 -0
  193. package/src/verax/observe/observe-runner.js +230 -0
  194. package/src/verax/observe/observers/budget-observer.js +185 -0
  195. package/src/verax/observe/observers/console-observer.js +102 -0
  196. package/src/verax/observe/observers/coverage-observer.js +107 -0
  197. package/src/verax/observe/observers/interaction-observer.js +471 -0
  198. package/src/verax/observe/observers/navigation-observer.js +132 -0
  199. package/src/verax/observe/observers/network-observer.js +87 -0
  200. package/src/verax/observe/observers/safety-observer.js +82 -0
  201. package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
  202. package/src/verax/observe/page-traversal.js +138 -0
  203. package/src/verax/observe/snapshot-ops.js +94 -0
  204. package/src/verax/observe/ui-feedback-detector.js +742 -0
  205. package/src/verax/scan-summary-writer.js +2 -0
  206. package/src/verax/shared/artifact-manager.js +25 -5
  207. package/src/verax/shared/caching.js +1 -0
  208. package/src/verax/shared/css-spinner-rules.js +204 -0
  209. package/src/verax/shared/expectation-tracker.js +1 -0
  210. package/src/verax/shared/view-switch-rules.js +208 -0
  211. package/src/verax/shared/zip-artifacts.js +6 -0
  212. package/src/verax/shared/config-loader.js +0 -169
  213. /package/src/verax/shared/{expectation-proof.js → expectation-validation.js} +0 -0
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Selector Resolver
3
+ * Finds and validates selectors for extracted promises
4
+ */
5
+
6
+ /**
7
+ * Resolve a selector to an actual element
8
+ * Returns {found: boolean, selector: string, reason?: string}
9
+ */
10
+ export async function resolveSelector(page, promise) {
11
+ // Try promise.selector first if it exists
12
+ if (promise.selector) {
13
+ const resolved = await trySelectorVariants(page, promise.selector);
14
+ if (resolved.found) {
15
+ return resolved;
16
+ }
17
+ }
18
+
19
+ // Try to derive selector from promise details
20
+ if (promise.category === 'button') {
21
+ return await resolveButtonSelector(page, promise);
22
+ }
23
+
24
+ if (promise.category === 'form') {
25
+ return await resolveFormSelector(page, promise);
26
+ }
27
+
28
+ if (promise.category === 'validation') {
29
+ return await resolveValidationSelector(page, promise);
30
+ }
31
+
32
+ if (promise.type === 'navigation') {
33
+ return await resolveNavigationSelector(page, promise);
34
+ }
35
+
36
+ return {
37
+ found: false,
38
+ selector: null,
39
+ reason: 'unsupported-promise-type',
40
+ };
41
+ }
42
+
43
+ /**
44
+ * Try multiple selector variants
45
+ */
46
+ async function trySelectorVariants(page, baseSelector) {
47
+ const variants = [
48
+ baseSelector,
49
+ baseSelector.replace(/button:contains/, 'button:has-text'),
50
+ baseSelector.replace(/"/g, "'"),
51
+ ];
52
+
53
+ for (const selector of variants) {
54
+ try {
55
+ const count = await page.locator(selector).count();
56
+ if (count === 1) {
57
+ return { found: true, selector };
58
+ }
59
+ if (count > 1) {
60
+ return { found: false, selector, reason: 'ambiguous-selector' };
61
+ }
62
+ } catch (e) {
63
+ // Try next variant
64
+ }
65
+ }
66
+
67
+ return { found: false, selector: baseSelector, reason: 'not-found' };
68
+ }
69
+
70
+ /**
71
+ * Resolve button selector
72
+ */
73
+ async function resolveButtonSelector(page, promise) {
74
+ // First try the provided selector
75
+ if (promise.selector) {
76
+ const result = await trySelectorVariants(page, promise.selector);
77
+ if (result.found) return result;
78
+ }
79
+
80
+ // Try all button variants
81
+ const selectors = [
82
+ 'button[onClick]',
83
+ 'button',
84
+ '[role="button"][onClick]',
85
+ '[role="button"]',
86
+ ];
87
+
88
+ for (const selector of selectors) {
89
+ try {
90
+ const count = await page.locator(selector).count();
91
+ if (count === 1) {
92
+ return { found: true, selector };
93
+ }
94
+ if (count > 0) {
95
+ // Multiple buttons - try to narrow down by content
96
+ const buttons = await page.locator(selector).all();
97
+ for (const btn of buttons) {
98
+ const text = await btn.textContent();
99
+ if (text && (text.includes('Save') || text.includes('Submit') || text.includes('Click'))) {
100
+ const specific = `${selector}:has-text("${text.trim()}")`;
101
+ const count = await page.locator(specific).count();
102
+ if (count === 1) {
103
+ return { found: true, selector: specific };
104
+ }
105
+ }
106
+ }
107
+ }
108
+ } catch (e) {
109
+ // Try next
110
+ }
111
+ }
112
+
113
+ return { found: false, selector: null, reason: 'not-found' };
114
+ }
115
+
116
+ /**
117
+ * Resolve form selector
118
+ */
119
+ async function resolveFormSelector(page, promise) {
120
+ if (promise.selector) {
121
+ const result = await trySelectorVariants(page, promise.selector);
122
+ if (result.found) return result;
123
+ }
124
+
125
+ const selectors = [
126
+ 'form[onSubmit]',
127
+ 'form',
128
+ ];
129
+
130
+ for (const selector of selectors) {
131
+ try {
132
+ const count = await page.locator(selector).count();
133
+ if (count === 1) {
134
+ return { found: true, selector };
135
+ }
136
+ } catch (e) {
137
+ // Try next
138
+ }
139
+ }
140
+
141
+ return { found: false, selector: null, reason: 'not-found' };
142
+ }
143
+
144
+ /**
145
+ * Resolve validation selector (for required inputs)
146
+ */
147
+ async function resolveValidationSelector(page, promise) {
148
+ if (promise.selector) {
149
+ const result = await trySelectorVariants(page, promise.selector);
150
+ if (result.found) return result;
151
+ }
152
+
153
+ // Look for required inputs
154
+ const selectors = [
155
+ 'input[required]',
156
+ 'textarea[required]',
157
+ 'select[required]',
158
+ ];
159
+
160
+ for (const selector of selectors) {
161
+ try {
162
+ const count = await page.locator(selector).count();
163
+ if (count >= 1) {
164
+ return { found: true, selector };
165
+ }
166
+ } catch (e) {
167
+ // Try next
168
+ }
169
+ }
170
+
171
+ return { found: false, selector: null, reason: 'not-found' };
172
+ }
173
+
174
+ /**
175
+ * Resolve navigation selector (for anchor tags)
176
+ */
177
+ async function resolveNavigationSelector(page, promise) {
178
+ const targetPath = promise.promise.value;
179
+
180
+ const selectors = [
181
+ `a[href="${targetPath}"]`,
182
+ `a[href*="${targetPath}"]`,
183
+ ];
184
+
185
+ for (const selector of selectors) {
186
+ try {
187
+ const count = await page.locator(selector).count();
188
+ if (count === 1) {
189
+ return { found: true, selector };
190
+ }
191
+ } catch (e) {
192
+ // Try next
193
+ }
194
+ }
195
+
196
+ return { found: false, selector: null, reason: 'not-found' };
197
+ }
198
+
199
+ /**
200
+ * Find submit button in form
201
+ */
202
+ export async function findSubmitButton(page, formSelector) {
203
+ try {
204
+ // Look for explicit submit button in form
205
+ const submitBtn = await page.locator(`${formSelector} button[type="submit"]`).first();
206
+ const count = await submitBtn.count();
207
+ if (count > 0) {
208
+ return submitBtn;
209
+ }
210
+
211
+ // Look for any button in form
212
+ const btn = await page.locator(`${formSelector} button`).first();
213
+ if (await btn.count() > 0) {
214
+ return btn;
215
+ }
216
+ } catch (e) {
217
+ // Fall through
218
+ }
219
+
220
+ return null;
221
+ }
222
+
223
+ /**
224
+ * Check if element is clickable/visible
225
+ */
226
+ export async function isElementInteractable(page, selector) {
227
+ try {
228
+ const element = page.locator(selector).first();
229
+ const isVisible = await element.isVisible();
230
+ const isEnabled = await element.isEnabled();
231
+ return isVisible && isEnabled;
232
+ } catch (e) {
233
+ return false;
234
+ }
235
+ }
@@ -0,0 +1,55 @@
1
+ import { existsSync, readdirSync, statSync } from 'fs';
2
+ import { extname, join, resolve } from 'path';
3
+ import { DataError } from './errors.js';
4
+ import { getSourceCodeRequirementBanner } from '../../verax/core/product-definition.js';
5
+
6
+ const CODE_EXTS = new Set(['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.html']);
7
+
8
+ function safeReaddir(dirPath) {
9
+ try {
10
+ return readdirSync(dirPath);
11
+ } catch {
12
+ return [];
13
+ }
14
+ }
15
+
16
+ function directoryHasCode(dirPath) {
17
+ const entries = safeReaddir(dirPath);
18
+ for (const entry of entries) {
19
+ const fullPath = join(dirPath, entry);
20
+ let stats;
21
+ try {
22
+ stats = statSync(fullPath);
23
+ } catch {
24
+ continue;
25
+ }
26
+
27
+ if (stats.isFile() && CODE_EXTS.has(extname(entry).toLowerCase())) {
28
+ return true;
29
+ }
30
+
31
+ if (stats.isDirectory() && (entry === 'src' || entry === 'app')) {
32
+ const nested = safeReaddir(fullPath).slice(0, 50);
33
+ if (nested.some((name) => CODE_EXTS.has(extname(name).toLowerCase()))) {
34
+ return true;
35
+ }
36
+ }
37
+ }
38
+ return false;
39
+ }
40
+
41
+ export function assertHasLocalSource(srcPath) {
42
+ const resolved = resolve(srcPath);
43
+ const hasPackageJson = existsSync(join(resolved, 'package.json'));
44
+ const hasIndexHtml = existsSync(join(resolved, 'index.html'));
45
+ const hasCodeFiles = directoryHasCode(resolved);
46
+
47
+ if (!hasPackageJson && !hasIndexHtml && !hasCodeFiles) {
48
+ const banner = getSourceCodeRequirementBanner();
49
+ throw new DataError(
50
+ `${banner} Provide --src pointing to your repository so VERAX can analyze expectations. See docs/README.md for the canonical product contract.`
51
+ );
52
+ }
53
+
54
+ return { hasPackageJson, hasIndexHtml, hasCodeFiles };
55
+ }
@@ -1,4 +1,5 @@
1
1
  import { atomicWriteJson } from './atomic-write.js';
2
+ import { ARTIFACT_REGISTRY } from '../../verax/core/artifacts/registry.js';
2
3
 
3
4
  /**
4
5
  * Write summary.json with deterministic digest
@@ -7,6 +8,7 @@ import { atomicWriteJson } from './atomic-write.js';
7
8
  */
8
9
  export function writeSummaryJson(summaryPath, summaryData, stats = {}) {
9
10
  const payload = {
11
+ contractVersion: ARTIFACT_REGISTRY.summary.contractVersion,
10
12
  runId: summaryData.runId,
11
13
  status: summaryData.status,
12
14
  startedAt: summaryData.startedAt,
@@ -0,0 +1,163 @@
1
+ /**
2
+ * PHASE 20 — Svelte Navigation Detector
3
+ *
4
+ * Detects navigation promises in Svelte applications:
5
+ * - <a href="/path"> links
6
+ * - goto() calls from SvelteKit
7
+ * - programmatic navigation
8
+ */
9
+
10
+ import { extractSvelteSFC, extractTemplateBindings as _extractTemplateBindings, mapTemplateHandlersToScript as _mapTemplateHandlersToScript } from './svelte-sfc-extractor.js';
11
+ import { parse } from '@babel/parser';
12
+ import traverse from '@babel/traverse';
13
+
14
+ /**
15
+ * Detect navigation promises in Svelte SFC
16
+ *
17
+ * @param {string} filePath - Path to .svelte file
18
+ * @param {string} content - Full file content
19
+ * @param {string} _projectRoot - Project root directory (unused)
20
+ * @returns {Array} Array of navigation expectations
21
+ */
22
+ export function detectSvelteNavigation(filePath, content, _projectRoot) {
23
+ const expectations = [];
24
+
25
+ try {
26
+ const sfc = extractSvelteSFC(content);
27
+ const { scriptBlocks, markup } = sfc;
28
+
29
+ // Extract navigation from markup (links)
30
+ if (markup && markup.content) {
31
+ const linkRegex = /<a\s+[^>]*href=["']([^"']+)["'][^>]*>/gi;
32
+ let linkMatch;
33
+ while ((linkMatch = linkRegex.exec(markup.content)) !== null) {
34
+ const href = linkMatch[1];
35
+ // Skip external links and hash-only links
36
+ if (href.startsWith('http://') || href.startsWith('https://') || href.startsWith('#')) {
37
+ continue;
38
+ }
39
+
40
+ const beforeMatch = markup.content.substring(0, linkMatch.index);
41
+ const line = markup.startLine + (beforeMatch.match(/\n/g) || []).length;
42
+
43
+ expectations.push({
44
+ type: 'navigation',
45
+ target: href,
46
+ context: 'markup',
47
+ sourceRef: {
48
+ file: filePath,
49
+ line,
50
+ snippet: linkMatch[0],
51
+ },
52
+ proof: 'PROVEN_EXPECTATION',
53
+ metadata: {
54
+ navigationType: 'link',
55
+ },
56
+ });
57
+ }
58
+ }
59
+
60
+ // Extract navigation from script blocks (goto, navigate, etc.)
61
+ for (const scriptBlock of scriptBlocks) {
62
+ if (!scriptBlock.content) continue;
63
+
64
+ try {
65
+ const ast = parse(scriptBlock.content, {
66
+ sourceType: 'module',
67
+ plugins: ['typescript', 'jsx'],
68
+ });
69
+
70
+ traverse.default(ast, {
71
+ CallExpression(path) {
72
+ const { node } = path;
73
+
74
+ // Detect goto() calls (SvelteKit)
75
+ if (
76
+ node.callee.type === 'Identifier' &&
77
+ node.callee.name === 'goto' &&
78
+ node.arguments.length > 0
79
+ ) {
80
+ const arg = node.arguments[0];
81
+ let target = null;
82
+
83
+ if (arg.type === 'StringLiteral') {
84
+ target = arg.value;
85
+ } else if (arg.type === 'TemplateLiteral' && arg.quasis.length === 1) {
86
+ target = arg.quasis[0].value.raw;
87
+ }
88
+
89
+ if (target && !target.startsWith('http://') && !target.startsWith('https://') && !target.startsWith('#')) {
90
+ const location = node.loc;
91
+ const line = scriptBlock.startLine + (location ? location.start.line - 1 : 0);
92
+
93
+ expectations.push({
94
+ type: 'navigation',
95
+ target,
96
+ context: 'goto',
97
+ sourceRef: {
98
+ file: filePath,
99
+ line,
100
+ snippet: scriptBlock.content.substring(
101
+ node.start - (ast.program.body[0]?.start || 0),
102
+ node.end - (ast.program.body[0]?.start || 0)
103
+ ),
104
+ },
105
+ proof: arg.type === 'StringLiteral' ? 'PROVEN_EXPECTATION' : 'LIKELY_EXPECTATION',
106
+ metadata: {
107
+ navigationType: 'goto',
108
+ },
109
+ });
110
+ }
111
+ }
112
+
113
+ // Detect navigate() calls (if imported)
114
+ if (
115
+ node.callee.type === 'MemberExpression' &&
116
+ node.callee.property.name === 'navigate' &&
117
+ node.arguments.length > 0
118
+ ) {
119
+ const arg = node.arguments[0];
120
+ let target = null;
121
+
122
+ if (arg.type === 'StringLiteral') {
123
+ target = arg.value;
124
+ } else if (arg.type === 'TemplateLiteral' && arg.quasis.length === 1) {
125
+ target = arg.quasis[0].value.raw;
126
+ }
127
+
128
+ if (target && !target.startsWith('http://') && !target.startsWith('https://') && !target.startsWith('#')) {
129
+ const location = node.loc;
130
+ const line = scriptBlock.startLine + (location ? location.start.line - 1 : 0);
131
+
132
+ expectations.push({
133
+ type: 'navigation',
134
+ target,
135
+ context: 'navigate',
136
+ sourceRef: {
137
+ file: filePath,
138
+ line,
139
+ snippet: scriptBlock.content.substring(
140
+ node.start - (ast.program.body[0]?.start || 0),
141
+ node.end - (ast.program.body[0]?.start || 0)
142
+ ),
143
+ },
144
+ proof: arg.type === 'StringLiteral' ? 'PROVEN_EXPECTATION' : 'LIKELY_EXPECTATION',
145
+ metadata: {
146
+ navigationType: 'navigate',
147
+ },
148
+ });
149
+ }
150
+ }
151
+ },
152
+ });
153
+ } catch (parseError) {
154
+ // Skip if parsing fails
155
+ }
156
+ }
157
+ } catch (error) {
158
+ // Skip if extraction fails
159
+ }
160
+
161
+ return expectations;
162
+ }
163
+
@@ -0,0 +1,80 @@
1
+ /**
2
+ * PHASE 20 — Svelte Network Detector
3
+ *
4
+ * Detects network calls (fetch, axios) in Svelte component handlers and lifecycle functions.
5
+ * Reuses AST network detector but ensures it works with Svelte SFC script blocks.
6
+ */
7
+
8
+ import { extractSvelteSFC, extractTemplateBindings, mapTemplateHandlersToScript } from './svelte-sfc-extractor.js';
9
+ import { detectNetworkCallsAST } from './ast-network-detector.js';
10
+ import { relative } from 'path';
11
+
12
+ /**
13
+ * Detect network promises in Svelte SFC
14
+ *
15
+ * @param {string} filePath - Path to .svelte file
16
+ * @param {string} content - Full file content
17
+ * @param {string} projectRoot - Project root directory
18
+ * @returns {Array} Array of network expectations
19
+ */
20
+ export function detectSvelteNetwork(filePath, content, projectRoot) {
21
+ const expectations = [];
22
+
23
+ try {
24
+ const sfc = extractSvelteSFC(content);
25
+ const { scriptBlocks, markup } = sfc;
26
+
27
+ // Extract event handlers from markup to identify UI-bound handlers
28
+ const templateBindings = markup ? extractTemplateBindings(markup.content) : { eventHandlers: [] };
29
+ const mappedHandlers = scriptBlocks.length > 0 && templateBindings.eventHandlers.length > 0
30
+ ? mapTemplateHandlersToScript(templateBindings.eventHandlers, scriptBlocks[0].content)
31
+ : [];
32
+
33
+ const uiBoundHandlers = new Set(mappedHandlers.map(h => h.handler));
34
+
35
+ // Process each script block
36
+ for (const scriptBlock of scriptBlocks) {
37
+ if (!scriptBlock.content) continue;
38
+
39
+ // Use AST network detector on script content
40
+ const networkCalls = detectNetworkCallsAST(scriptBlock.content, filePath, relative(projectRoot, filePath));
41
+
42
+ // Filter and enhance network calls
43
+ for (const networkCall of networkCalls) {
44
+ // Check if this is in a UI-bound handler
45
+ const isUIBound = networkCall.context && uiBoundHandlers.has(networkCall.context);
46
+
47
+ // Skip analytics-only calls (filtered by guardrails later)
48
+ if (networkCall.target && (
49
+ networkCall.target.includes('/api/analytics') ||
50
+ networkCall.target.includes('/api/track') ||
51
+ networkCall.target.includes('/api/beacon')
52
+ )) {
53
+ continue;
54
+ }
55
+
56
+ expectations.push({
57
+ type: 'network',
58
+ target: networkCall.target,
59
+ method: networkCall.method || 'GET',
60
+ context: networkCall.context || 'component',
61
+ sourceRef: {
62
+ file: filePath,
63
+ line: networkCall.line || scriptBlock.startLine,
64
+ snippet: networkCall.snippet || '',
65
+ },
66
+ proof: networkCall.proof || 'LIKELY_EXPECTATION',
67
+ metadata: {
68
+ isUIBound,
69
+ handlerContext: networkCall.context,
70
+ },
71
+ });
72
+ }
73
+ }
74
+ } catch (error) {
75
+ // Skip if extraction fails
76
+ }
77
+
78
+ return expectations;
79
+ }
80
+
@@ -0,0 +1,146 @@
1
+ /**
2
+ * PHASE 20 — Svelte SFC (Single File Component) Extractor
3
+ *
4
+ * Extracts <script>, <script context="module">, and markup content from .svelte files.
5
+ * Deterministic and robust (no external runtime execution).
6
+ */
7
+
8
+ /**
9
+ * PHASE 20: Extract Svelte SFC blocks
10
+ *
11
+ * @param {string} content - Full .svelte file content
12
+ * @returns {Object} { scriptBlocks: [{content, lang, startLine, isModule}], markup: {content, startLine} }
13
+ */
14
+ export function extractSvelteSFC(content) {
15
+ const scriptBlocks = [];
16
+ let markup = null;
17
+
18
+ // Extract <script> blocks (including <script context="module">)
19
+ const scriptRegex = /<script(?:\s+context=["']module["'])?(?:\s+lang=["']([^"']+)["'])?[^>]*>([\s\S]*?)<\/script>/gi;
20
+ let scriptMatch;
21
+
22
+ while ((scriptMatch = scriptRegex.exec(content)) !== null) {
23
+ const isModule = scriptMatch[0].includes('context="module"') || scriptMatch[0].includes("context='module'");
24
+ const lang = scriptMatch[1] || 'js';
25
+ const scriptContent = scriptMatch[2];
26
+
27
+ // Calculate start line
28
+ const beforeMatch = content.substring(0, scriptMatch.index);
29
+ const startLine = (beforeMatch.match(/\n/g) || []).length + 1;
30
+
31
+ scriptBlocks.push({
32
+ content: scriptContent.trim(),
33
+ lang: lang.toLowerCase(),
34
+ startLine,
35
+ isModule,
36
+ });
37
+ }
38
+
39
+ // Extract markup (everything outside script/style tags)
40
+ // Svelte markup is the template content
41
+ const styleRegex = /<style[^>]*>[\s\S]*?<\/style>/gi;
42
+ const allScriptRegex = /<script[^>]*>[\s\S]*?<\/script>/gi;
43
+
44
+ let markupContent = content;
45
+ // Remove style blocks
46
+ markupContent = markupContent.replace(styleRegex, '');
47
+ // Remove script blocks
48
+ markupContent = markupContent.replace(allScriptRegex, '');
49
+
50
+ // Find first non-whitespace line for markup
51
+ const lines = content.split('\n');
52
+ let markupStartLine = 1;
53
+ for (let i = 0; i < lines.length; i++) {
54
+ const line = lines[i];
55
+ if (line.trim() && !line.trim().startsWith('<script') && !line.trim().startsWith('<style')) {
56
+ markupStartLine = i + 1;
57
+ break;
58
+ }
59
+ }
60
+
61
+ if (markupContent.trim()) {
62
+ markup = {
63
+ content: markupContent.trim(),
64
+ startLine: markupStartLine,
65
+ };
66
+ }
67
+
68
+ return {
69
+ scriptBlocks,
70
+ markup,
71
+ };
72
+ }
73
+
74
+ /**
75
+ * Extract template bindings from Svelte markup
76
+ * Detects reactive statements, event handlers, and bindings
77
+ *
78
+ * @param {string} markupContent - Svelte markup content
79
+ * @returns {Object} { reactiveStatements: [], eventHandlers: [], bindings: [] }
80
+ */
81
+ export function extractTemplateBindings(markupContent) {
82
+ const reactiveStatements = [];
83
+ const eventHandlers = [];
84
+ const bindings = [];
85
+
86
+ // Extract reactive statements: $: statements
87
+ const reactiveRegex = /\$:\s*([^;]+);/g;
88
+ let reactiveMatch;
89
+ while ((reactiveMatch = reactiveRegex.exec(markupContent)) !== null) {
90
+ reactiveStatements.push({
91
+ statement: reactiveMatch[1].trim(),
92
+ line: (markupContent.substring(0, reactiveMatch.index).match(/\n/g) || []).length + 1,
93
+ });
94
+ }
95
+
96
+ // Extract event handlers: on:click, on:submit, etc.
97
+ const eventHandlerRegex = /on:(\w+)=["']([^"']+)["']/g;
98
+ let handlerMatch;
99
+ while ((handlerMatch = eventHandlerRegex.exec(markupContent)) !== null) {
100
+ eventHandlers.push({
101
+ event: handlerMatch[1],
102
+ handler: handlerMatch[2],
103
+ line: (markupContent.substring(0, handlerMatch.index).match(/\n/g) || []).length + 1,
104
+ });
105
+ }
106
+
107
+ // Extract bindings: bind:value, bind:checked, etc.
108
+ const bindingRegex = /bind:(\w+)=["']([^"']+)["']/g;
109
+ let bindingMatch;
110
+ while ((bindingMatch = bindingRegex.exec(markupContent)) !== null) {
111
+ bindings.push({
112
+ property: bindingMatch[1],
113
+ variable: bindingMatch[2],
114
+ line: (markupContent.substring(0, bindingMatch.index).match(/\n/g) || []).length + 1,
115
+ });
116
+ }
117
+
118
+ return {
119
+ reactiveStatements,
120
+ eventHandlers,
121
+ bindings,
122
+ };
123
+ }
124
+
125
+ /**
126
+ * Map template handlers to script functions
127
+ * Helps identify which handlers are UI-bound
128
+ *
129
+ * @param {Array} eventHandlers - Event handlers from template
130
+ * @param {string} scriptContent - Script block content
131
+ * @returns {Array} Mapped handlers with function references
132
+ */
133
+ export function mapTemplateHandlersToScript(eventHandlers, scriptContent) {
134
+ return eventHandlers.map(handler => {
135
+ // Try to find function definition in script
136
+ const functionRegex = new RegExp(`(?:function\\s+${handler.handler}|const\\s+${handler.handler}\\s*=\\s*[^(]*\\(|${handler.handler}\\s*=\\s*[^(]*\\(|export\\s+function\\s+${handler.handler})`, 'g');
137
+ const functionMatch = functionRegex.exec(scriptContent);
138
+
139
+ return {
140
+ ...handler,
141
+ functionFound: !!functionMatch,
142
+ functionLine: functionMatch ? (scriptContent.substring(0, functionMatch.index).match(/\n/g) || []).length + 1 : null,
143
+ };
144
+ });
145
+ }
146
+