@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,581 @@
1
+ import { parse } from '@babel/parser';
2
+ import _traverse from '@babel/traverse';
3
+ import { readFileSync as _readFileSync } from 'fs';
4
+
5
+ // Handle default export from @babel/traverse (CommonJS/ESM compatibility)
6
+ const traverse = _traverse.default || _traverse;
7
+
8
+ /**
9
+ * PHASE H2/M2 — AST-Based Promise Extraction
10
+ *
11
+ * Extracts user promises from code using real AST parsing:
12
+ * - Button interactions (<button>, role="button", onClick)
13
+ * - Forms & submission (<form>, onSubmit, preventDefault)
14
+ * - Validation promises (required, disabled, validation branches)
15
+ * - UI feedback (toasts, alerts, aria-live, status banners)
16
+ *
17
+ * Evidence-first: Only emit promises we can prove exist in code
18
+ * Deterministic: Same code → same promises every run
19
+ */
20
+
21
+ /**
22
+ * Extract promises from JSX/TSX file using AST
23
+ */
24
+ export function extractPromisesFromAST(content, filePath, relPath) {
25
+ const promises = [];
26
+
27
+ try {
28
+ const ast = parse(content, {
29
+ sourceType: 'module',
30
+ plugins: [
31
+ 'jsx',
32
+ 'typescript',
33
+ 'classProperties',
34
+ 'optionalChaining',
35
+ 'nullishCoalescingOperator',
36
+ 'dynamicImport',
37
+ ['decorators', { decoratorsBeforeExport: true }],
38
+ 'topLevelAwait',
39
+ 'objectRestSpread',
40
+ 'asyncGenerators',
41
+ ],
42
+ errorRecovery: true,
43
+ });
44
+
45
+ const context = {
46
+ filePath,
47
+ relPath,
48
+ lines: content.split('\n'),
49
+ imports: trackImports(ast),
50
+ formElements: new Map(), // Track form refs for submit button association
51
+ };
52
+
53
+ traverse(ast, {
54
+ // Button interactions
55
+ JSXElement(path) {
56
+ extractButtonPromises(path, context, promises);
57
+ extractFormPromises(path, context, promises);
58
+ extractValidationInputPromises(path, context, promises);
59
+ extractFeedbackPromises(path, context, promises);
60
+ },
61
+
62
+ // Validation logic in handlers
63
+ FunctionDeclaration(path) {
64
+ extractValidationPromises(path, context, promises);
65
+ },
66
+ ArrowFunctionExpression(path) {
67
+ extractValidationPromises(path, context, promises);
68
+ },
69
+ FunctionExpression(path) {
70
+ extractValidationPromises(path, context, promises);
71
+ },
72
+ });
73
+ } catch (error) {
74
+ // Parse error - skip this file silently
75
+ }
76
+
77
+ return promises;
78
+ }
79
+
80
+ /**
81
+ * Track imports for context (router, toast libraries, etc.)
82
+ */
83
+ function trackImports(ast) {
84
+ const imports = {
85
+ router: new Set(),
86
+ navigation: new Set(),
87
+ toast: new Set(),
88
+ modal: new Set(),
89
+ };
90
+
91
+ traverse(ast, {
92
+ ImportDeclaration(path) {
93
+ const source = path.node.source.value;
94
+
95
+ // React Router
96
+ if (source.includes('react-router')) {
97
+ path.node.specifiers.forEach((spec) => {
98
+ if (spec.type === 'ImportSpecifier' || spec.type === 'ImportDefaultSpecifier') {
99
+ imports.router.add(spec.local.name);
100
+ if (['useNavigate', 'useHistory'].includes(spec.imported?.name)) {
101
+ imports.navigation.add(spec.local.name);
102
+ }
103
+ }
104
+ });
105
+ }
106
+
107
+ // Next.js
108
+ if (source.includes('next/')) {
109
+ path.node.specifiers.forEach((spec) => {
110
+ if (spec.type === 'ImportSpecifier' || spec.type === 'ImportDefaultSpecifier') {
111
+ if (source.includes('next/link')) imports.router.add(spec.local.name);
112
+ if (source.includes('next/navigation')) imports.navigation.add(spec.local.name);
113
+ }
114
+ });
115
+ }
116
+
117
+ // Toast libraries (generic detection)
118
+ if (source.includes('toast') || source.includes('notification') ||
119
+ source.includes('alert') || source.includes('snackbar')) {
120
+ path.node.specifiers.forEach((spec) => {
121
+ if (spec.local) imports.toast.add(spec.local.name);
122
+ });
123
+ }
124
+
125
+ // Modal libraries
126
+ if (source.includes('modal') || source.includes('dialog')) {
127
+ path.node.specifiers.forEach((spec) => {
128
+ if (spec.local) imports.modal.add(spec.local.name);
129
+ });
130
+ }
131
+ },
132
+ });
133
+
134
+ return imports;
135
+ }
136
+
137
+ /**
138
+ * Extract button interaction promises
139
+ */
140
+ function extractButtonPromises(path, context, promises) {
141
+ const opening = path.node.openingElement;
142
+ const elementName = opening.name.name || opening.name.property?.name;
143
+
144
+ // Check if this is a button element
145
+ const isButton = elementName === 'button' ||
146
+ hasAttribute(opening, 'role', 'button') ||
147
+ elementName === 'Button'; // Common component name
148
+
149
+ if (!isButton) return;
150
+
151
+ // Look for onClick handler
152
+ const onClickAttr = opening.attributes.find(
153
+ attr => attr.type === 'JSXAttribute' && attr.name.name === 'onClick'
154
+ );
155
+
156
+ if (!onClickAttr) return;
157
+
158
+ const loc = path.node.loc;
159
+ if (!loc) return;
160
+
161
+ // Check if button has type="submit" - if so, skip (handled by form extraction)
162
+ const typeAttr = opening.attributes.find(
163
+ attr => attr.type === 'JSXAttribute' && attr.name.name === 'type'
164
+ );
165
+ if (typeAttr?.value?.value === 'submit') return;
166
+
167
+ // Extract button text/label for selector
168
+ const buttonText = extractElementText(path.node);
169
+ const selector = buttonText ? `button:contains("${buttonText}")` : 'button[onClick]';
170
+
171
+ // Check if handler contains navigation
172
+ const handlerSource = extractHandlerSource(onClickAttr.value, context);
173
+ const hasNavigationCall = detectNavigationInSource(handlerSource, context);
174
+
175
+ promises.push({
176
+ category: 'button',
177
+ type: 'interaction',
178
+ promise: {
179
+ kind: 'click',
180
+ value: buttonText || 'button click',
181
+ },
182
+ source: {
183
+ file: context.relPath,
184
+ line: loc.start.line,
185
+ column: loc.start.column,
186
+ },
187
+ selector,
188
+ action: 'click',
189
+ expectedOutcome: hasNavigationCall ? 'navigation' : 'ui-change',
190
+ confidenceHint: 'medium',
191
+ });
192
+ }
193
+
194
+ /**
195
+ * Extract form submission promises
196
+ */
197
+ function extractFormPromises(path, context, promises) {
198
+ const opening = path.node.openingElement;
199
+ const elementName = opening.name.name || opening.name.property?.name;
200
+
201
+ if (elementName !== 'form' && elementName !== 'Form') return;
202
+
203
+ // Look for onSubmit handler
204
+ const onSubmitAttr = opening.attributes.find(
205
+ attr => attr.type === 'JSXAttribute' && attr.name.name === 'onSubmit'
206
+ );
207
+
208
+ if (!onSubmitAttr) return;
209
+
210
+ const loc = path.node.loc;
211
+ if (!loc) return;
212
+
213
+ // Extract handler source
214
+ const handlerSource = extractHandlerSource(onSubmitAttr.value, context);
215
+
216
+ // Check for preventDefault (indicates form handling, not default browser submit)
217
+ const hasPreventDefault = handlerSource.includes('preventDefault');
218
+
219
+ // Check for validation
220
+ const hasValidation = detectValidationInSource(handlerSource);
221
+
222
+ // Check for navigation
223
+ const hasNavigation = detectNavigationInSource(handlerSource, context);
224
+
225
+ // Check for network calls
226
+ const hasNetwork = detectNetworkInSource(handlerSource);
227
+
228
+ // Determine expected outcome
229
+ let expectedOutcome = 'ui-change';
230
+ if (hasNetwork) expectedOutcome = 'network';
231
+ if (hasNavigation) expectedOutcome = 'navigation';
232
+
233
+ promises.push({
234
+ category: 'form',
235
+ type: 'interaction',
236
+ promise: {
237
+ kind: 'submit',
238
+ value: 'form submission',
239
+ },
240
+ source: {
241
+ file: context.relPath,
242
+ line: loc.start.line,
243
+ column: loc.start.column,
244
+ },
245
+ selector: 'form[onSubmit]',
246
+ action: 'submit',
247
+ expectedOutcome,
248
+ confidenceHint: hasPreventDefault ? 'medium' : 'low',
249
+ });
250
+
251
+ // If validation detected, add validation promise
252
+ if (hasValidation) {
253
+ promises.push({
254
+ category: 'validation',
255
+ type: 'feedback',
256
+ promise: {
257
+ kind: 'validation',
258
+ value: 'input validation feedback',
259
+ },
260
+ source: {
261
+ file: context.relPath,
262
+ line: loc.start.line,
263
+ column: loc.start.column,
264
+ },
265
+ selector: 'form[onSubmit]',
266
+ action: 'observe',
267
+ expectedOutcome: 'feedback',
268
+ confidenceHint: 'medium',
269
+ });
270
+ }
271
+ }
272
+
273
+ /**
274
+ * Extract validation promises from input elements with required attribute
275
+ */
276
+ function extractValidationInputPromises(path, context, promises) {
277
+ const opening = path.node.openingElement;
278
+ const elementName = opening.name.name || opening.name.property?.name;
279
+
280
+ // Check if this is an input element
281
+ if (elementName !== 'input' && elementName !== 'Input') return;
282
+
283
+ // Check for required attribute
284
+ const hasRequired = opening.attributes.some(
285
+ attr => attr.type === 'JSXAttribute' && attr.name.name === 'required'
286
+ );
287
+
288
+ if (!hasRequired) return;
289
+
290
+ const loc = path.node.loc;
291
+ if (!loc) return;
292
+
293
+ // Extract name or id for selector
294
+ const nameAttr = opening.attributes.find(
295
+ attr => attr.type === 'JSXAttribute' && attr.name.name === 'name'
296
+ );
297
+ const idAttr = opening.attributes.find(
298
+ attr => attr.type === 'JSXAttribute' && attr.name.name === 'id'
299
+ );
300
+
301
+ let selector = 'input[required]';
302
+ if (nameAttr?.value?.value) {
303
+ selector = `input[name="${nameAttr.value.value}"]`;
304
+ } else if (idAttr?.value?.value) {
305
+ selector = `input#${idAttr.value.value}`;
306
+ }
307
+
308
+ promises.push({
309
+ category: 'validation',
310
+ type: 'feedback',
311
+ promise: {
312
+ kind: 'validation',
313
+ value: 'required field validation',
314
+ },
315
+ source: {
316
+ file: context.relPath,
317
+ line: loc.start.line,
318
+ column: loc.start.column,
319
+ },
320
+ selector,
321
+ action: 'observe',
322
+ expectedOutcome: 'feedback',
323
+ confidenceHint: 'medium',
324
+ });
325
+ }
326
+
327
+ /**
328
+ * Extract validation promises from code
329
+ */
330
+ function extractValidationPromises(path, context, promises) {
331
+ const body = path.node.body;
332
+ if (!body) return;
333
+
334
+ const source = extractFunctionSource(path, context);
335
+ if (!source) return;
336
+
337
+ // Look for validation patterns
338
+ const hasValidationLogic = (
339
+ source.includes('required') ||
340
+ source.includes('validate') ||
341
+ source.includes('.length') ||
342
+ source.includes('isEmpty') ||
343
+ source.includes('isValid') ||
344
+ source.includes('error') && (source.includes('set') || source.includes('Error'))
345
+ );
346
+
347
+ if (!hasValidationLogic) return;
348
+
349
+ // Check if this function is used as a form handler or validator
350
+ const isValidationContext = (
351
+ path.node.id?.name?.toLowerCase().includes('valid') ||
352
+ path.node.id?.name?.toLowerCase().includes('check') ||
353
+ path.node.id?.name?.toLowerCase().includes('submit') ||
354
+ source.includes('onSubmit') ||
355
+ source.includes('onChange')
356
+ );
357
+
358
+ if (!isValidationContext) return;
359
+
360
+ const loc = path.node.loc;
361
+ if (!loc) return;
362
+
363
+ promises.push({
364
+ category: 'validation',
365
+ type: 'feedback',
366
+ promise: {
367
+ kind: 'validation',
368
+ value: 'validation feedback',
369
+ },
370
+ source: {
371
+ file: context.relPath,
372
+ line: loc.start.line,
373
+ column: loc.start.column,
374
+ },
375
+ selector: null, // Cannot determine selector from handler alone
376
+ action: 'observe',
377
+ expectedOutcome: 'feedback',
378
+ confidenceHint: 'low',
379
+ });
380
+ }
381
+
382
+ /**
383
+ * Extract UI feedback promises
384
+ */
385
+ function extractFeedbackPromises(path, context, promises) {
386
+ const opening = path.node.openingElement;
387
+
388
+ // Check for aria-live regions (explicit feedback promise)
389
+ const ariaLiveAttr = opening.attributes.find(
390
+ attr => attr.type === 'JSXAttribute' && attr.name.name === 'aria-live'
391
+ );
392
+
393
+ if (ariaLiveAttr) {
394
+ const loc = path.node.loc;
395
+ if (!loc) return;
396
+
397
+ promises.push({
398
+ category: 'feedback',
399
+ type: 'feedback',
400
+ promise: {
401
+ kind: 'ui-feedback',
402
+ value: 'live region update',
403
+ },
404
+ source: {
405
+ file: context.relPath,
406
+ line: loc.start.line,
407
+ column: loc.start.column,
408
+ },
409
+ selector: '[aria-live]',
410
+ action: 'observe',
411
+ expectedOutcome: 'feedback',
412
+ confidenceHint: 'medium',
413
+ });
414
+ }
415
+
416
+ // Check for role="status" or role="alert"
417
+ const roleAttr = opening.attributes.find(
418
+ attr => attr.type === 'JSXAttribute' && attr.name.name === 'role'
419
+ );
420
+
421
+ if (roleAttr?.value?.value === 'status' || roleAttr?.value?.value === 'alert') {
422
+ const loc = path.node.loc;
423
+ if (!loc) return;
424
+
425
+ promises.push({
426
+ category: 'feedback',
427
+ type: 'feedback',
428
+ promise: {
429
+ kind: 'ui-feedback',
430
+ value: `${roleAttr.value.value} message`,
431
+ },
432
+ source: {
433
+ file: context.relPath,
434
+ line: loc.start.line,
435
+ column: loc.start.column,
436
+ },
437
+ selector: `[role="${roleAttr.value.value}"]`,
438
+ action: 'observe',
439
+ expectedOutcome: 'feedback',
440
+ confidenceHint: 'medium',
441
+ });
442
+ }
443
+ }
444
+
445
+ /**
446
+ * Extract handler source code from JSX attribute value
447
+ */
448
+ function extractHandlerSource(value, context) {
449
+ if (!value) return '';
450
+
451
+ if (value.type === 'JSXExpressionContainer') {
452
+ const expr = value.expression;
453
+
454
+ // Inline arrow function
455
+ if (expr.type === 'ArrowFunctionExpression' || expr.type === 'FunctionExpression') {
456
+ const loc = expr.loc;
457
+ if (!loc) return '';
458
+ return context.lines.slice(loc.start.line - 1, loc.end.line).join('\n');
459
+ }
460
+
461
+ // Reference to function
462
+ if (expr.type === 'Identifier') {
463
+ return expr.name; // Return function name for pattern matching
464
+ }
465
+
466
+ // Member expression (e.g., this.handleClick)
467
+ if (expr.type === 'MemberExpression') {
468
+ return `${expr.object.name}.${expr.property.name}`;
469
+ }
470
+ }
471
+
472
+ return '';
473
+ }
474
+
475
+ /**
476
+ * Extract function source code
477
+ */
478
+ function extractFunctionSource(path, context) {
479
+ const loc = path.node.loc;
480
+ if (!loc) return '';
481
+
482
+ return context.lines.slice(loc.start.line - 1, loc.end.line).join('\n');
483
+ }
484
+
485
+ /**
486
+ * Extract text content from JSX element
487
+ */
488
+ function extractElementText(node) {
489
+ if (!node.children || node.children.length === 0) return '';
490
+
491
+ for (const child of node.children) {
492
+ if (child.type === 'JSXText') {
493
+ return child.value.trim();
494
+ }
495
+ if (child.type === 'JSXExpressionContainer' && child.expression.type === 'StringLiteral') {
496
+ return child.expression.value;
497
+ }
498
+ }
499
+
500
+ return '';
501
+ }
502
+
503
+ /**
504
+ * Check if element has specific attribute with value
505
+ */
506
+ function hasAttribute(opening, attrName, attrValue = null) {
507
+ const attr = opening.attributes.find(
508
+ a => a.type === 'JSXAttribute' && a.name.name === attrName
509
+ );
510
+
511
+ if (!attr) return false;
512
+ if (attrValue === null) return true;
513
+
514
+ return attr.value?.value === attrValue;
515
+ }
516
+
517
+ /**
518
+ * Detect navigation calls in source code
519
+ */
520
+ function detectNavigationInSource(source, context) {
521
+ if (!source) return false;
522
+
523
+ return (
524
+ source.includes('.push(') ||
525
+ source.includes('.replace(') ||
526
+ source.includes('navigate(') ||
527
+ source.includes('router.') ||
528
+ source.includes('history.') ||
529
+ Array.from(context.imports.navigation).some(name => source.includes(name))
530
+ );
531
+ }
532
+
533
+ /**
534
+ * Detect validation logic in source code
535
+ */
536
+ function detectValidationInSource(source) {
537
+ if (!source) return false;
538
+
539
+ return (
540
+ source.includes('required') ||
541
+ source.includes('validate') ||
542
+ source.includes('isValid') ||
543
+ source.includes('isEmpty') ||
544
+ (source.includes('error') && (source.includes('set') || source.includes('Error'))) ||
545
+ source.includes('pattern') ||
546
+ source.includes('minLength') ||
547
+ source.includes('maxLength')
548
+ );
549
+ }
550
+
551
+ /**
552
+ * Detect network calls in source code
553
+ */
554
+ function detectNetworkInSource(source) {
555
+ if (!source) return false;
556
+
557
+ return (
558
+ source.includes('fetch(') ||
559
+ source.includes('axios.') ||
560
+ source.includes('.post(') ||
561
+ source.includes('.get(') ||
562
+ source.includes('.put(') ||
563
+ source.includes('.delete(') ||
564
+ source.includes('api.')
565
+ );
566
+ }
567
+
568
+ /**
569
+ * Detect toast/notification calls in source code
570
+ */
571
+ function _detectToastInSource(source, context) {
572
+ if (!source) return false;
573
+
574
+ return (
575
+ source.includes('toast.') ||
576
+ source.includes('notify(') ||
577
+ source.includes('alert(') ||
578
+ source.includes('showMessage') ||
579
+ Array.from(context.imports.toast).some(name => source.includes(name))
580
+ );
581
+ }