@veraxhq/verax 0.3.0 → 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 (191) hide show
  1. package/README.md +28 -20
  2. package/bin/verax.js +11 -18
  3. package/package.json +28 -7
  4. package/src/cli/commands/baseline.js +1 -2
  5. package/src/cli/commands/default.js +72 -81
  6. package/src/cli/commands/doctor.js +29 -0
  7. package/src/cli/commands/ga.js +3 -0
  8. package/src/cli/commands/gates.js +1 -1
  9. package/src/cli/commands/inspect.js +6 -133
  10. package/src/cli/commands/release-check.js +2 -0
  11. package/src/cli/commands/run.js +74 -246
  12. package/src/cli/commands/security-check.js +2 -1
  13. package/src/cli/commands/truth.js +0 -1
  14. package/src/cli/entry.js +82 -309
  15. package/src/cli/util/angular-component-extractor.js +2 -2
  16. package/src/cli/util/angular-navigation-detector.js +2 -2
  17. package/src/cli/util/ast-interactive-detector.js +4 -6
  18. package/src/cli/util/ast-network-detector.js +3 -3
  19. package/src/cli/util/ast-promise-extractor.js +581 -0
  20. package/src/cli/util/ast-usestate-detector.js +3 -3
  21. package/src/cli/util/atomic-write.js +12 -1
  22. package/src/cli/util/console-reporter.js +72 -0
  23. package/src/cli/util/detection-engine.js +105 -41
  24. package/src/cli/util/determinism-runner.js +2 -1
  25. package/src/cli/util/determinism-writer.js +1 -1
  26. package/src/cli/util/digest-engine.js +359 -0
  27. package/src/cli/util/dom-diff.js +226 -0
  28. package/src/cli/util/env-url.js +0 -4
  29. package/src/cli/util/evidence-engine.js +287 -0
  30. package/src/cli/util/expectation-extractor.js +217 -367
  31. package/src/cli/util/findings-writer.js +19 -126
  32. package/src/cli/util/framework-detector.js +572 -0
  33. package/src/cli/util/idgen.js +1 -1
  34. package/src/cli/util/interaction-planner.js +529 -0
  35. package/src/cli/util/learn-writer.js +2 -2
  36. package/src/cli/util/ledger-writer.js +110 -0
  37. package/src/cli/util/monorepo-resolver.js +162 -0
  38. package/src/cli/util/observation-engine.js +127 -278
  39. package/src/cli/util/observe-writer.js +2 -2
  40. package/src/cli/util/paths.js +12 -3
  41. package/src/cli/util/project-discovery.js +284 -3
  42. package/src/cli/util/project-writer.js +2 -2
  43. package/src/cli/util/run-id.js +23 -27
  44. package/src/cli/util/run-result.js +778 -0
  45. package/src/cli/util/selector-resolver.js +235 -0
  46. package/src/cli/util/summary-writer.js +2 -1
  47. package/src/cli/util/svelte-navigation-detector.js +3 -3
  48. package/src/cli/util/svelte-sfc-extractor.js +0 -1
  49. package/src/cli/util/svelte-state-detector.js +1 -2
  50. package/src/cli/util/trust-activation-integration.js +496 -0
  51. package/src/cli/util/trust-activation-wrapper.js +85 -0
  52. package/src/cli/util/trust-integration-hooks.js +164 -0
  53. package/src/cli/util/types.js +153 -0
  54. package/src/cli/util/url-validation.js +40 -0
  55. package/src/cli/util/vue-navigation-detector.js +4 -3
  56. package/src/cli/util/vue-sfc-extractor.js +1 -2
  57. package/src/cli/util/vue-state-detector.js +1 -1
  58. package/src/types/fs-augment.d.ts +23 -0
  59. package/src/types/global.d.ts +137 -0
  60. package/src/types/internal-types.d.ts +35 -0
  61. package/src/verax/cli/finding-explainer.js +3 -56
  62. package/src/verax/cli/init.js +4 -18
  63. package/src/verax/core/action-classifier.js +4 -3
  64. package/src/verax/core/artifacts/registry.js +0 -15
  65. package/src/verax/core/artifacts/verifier.js +18 -8
  66. package/src/verax/core/baseline/baseline.snapshot.js +2 -0
  67. package/src/verax/core/capabilities/gates.js +7 -1
  68. package/src/verax/core/confidence/confidence-compute.js +14 -7
  69. package/src/verax/core/confidence/confidence.loader.js +1 -0
  70. package/src/verax/core/confidence-engine-refactor.js +8 -3
  71. package/src/verax/core/confidence-engine.js +162 -23
  72. package/src/verax/core/contracts/types.js +1 -0
  73. package/src/verax/core/contracts/validators.js +79 -4
  74. package/src/verax/core/decision-snapshot.js +3 -30
  75. package/src/verax/core/decisions/decision.trace.js +2 -0
  76. package/src/verax/core/determinism/contract-writer.js +2 -2
  77. package/src/verax/core/determinism/contract.js +1 -1
  78. package/src/verax/core/determinism/diff.js +42 -1
  79. package/src/verax/core/determinism/engine.js +7 -6
  80. package/src/verax/core/determinism/finding-identity.js +3 -2
  81. package/src/verax/core/determinism/normalize.js +32 -4
  82. package/src/verax/core/determinism/report-writer.js +1 -0
  83. package/src/verax/core/determinism/run-fingerprint.js +7 -2
  84. package/src/verax/core/dynamic-route-intelligence.js +8 -7
  85. package/src/verax/core/evidence/evidence-capture-service.js +1 -0
  86. package/src/verax/core/evidence/evidence-intent-ledger.js +2 -1
  87. package/src/verax/core/evidence-builder.js +2 -2
  88. package/src/verax/core/execution-mode-context.js +1 -1
  89. package/src/verax/core/execution-mode-detector.js +5 -3
  90. package/src/verax/core/failures/exit-codes.js +39 -37
  91. package/src/verax/core/failures/failure-summary.js +1 -1
  92. package/src/verax/core/failures/failure.factory.js +3 -3
  93. package/src/verax/core/failures/failure.ledger.js +3 -2
  94. package/src/verax/core/ga/ga.artifact.js +1 -1
  95. package/src/verax/core/ga/ga.contract.js +3 -2
  96. package/src/verax/core/ga/ga.enforcer.js +1 -0
  97. package/src/verax/core/guardrails/policy.loader.js +1 -0
  98. package/src/verax/core/guardrails/truth-reconciliation.js +1 -1
  99. package/src/verax/core/guardrails-engine.js +2 -2
  100. package/src/verax/core/incremental-store.js +1 -0
  101. package/src/verax/core/integrity/budget.js +138 -0
  102. package/src/verax/core/integrity/determinism.js +342 -0
  103. package/src/verax/core/integrity/integrity.js +208 -0
  104. package/src/verax/core/integrity/poisoning.js +108 -0
  105. package/src/verax/core/integrity/transaction.js +140 -0
  106. package/src/verax/core/observe/run-timeline.js +2 -0
  107. package/src/verax/core/perf/perf.report.js +2 -0
  108. package/src/verax/core/pipeline-tracker.js +5 -0
  109. package/src/verax/core/release/provenance.builder.js +73 -214
  110. package/src/verax/core/release/release.enforcer.js +14 -9
  111. package/src/verax/core/release/reproducibility.check.js +1 -0
  112. package/src/verax/core/release/sbom.builder.js +32 -23
  113. package/src/verax/core/replay-validator.js +2 -0
  114. package/src/verax/core/replay.js +4 -0
  115. package/src/verax/core/report/cross-index.js +6 -3
  116. package/src/verax/core/report/human-summary.js +141 -1
  117. package/src/verax/core/route-intelligence.js +4 -3
  118. package/src/verax/core/run-id.js +6 -3
  119. package/src/verax/core/run-manifest.js +4 -3
  120. package/src/verax/core/security/secrets.scan.js +10 -7
  121. package/src/verax/core/security/security.enforcer.js +4 -0
  122. package/src/verax/core/security/supplychain.policy.js +9 -1
  123. package/src/verax/core/security/vuln.scan.js +2 -2
  124. package/src/verax/core/truth/truth.certificate.js +3 -1
  125. package/src/verax/core/ui-feedback-intelligence.js +12 -46
  126. package/src/verax/detect/conditional-ui-silent-failure.js +84 -0
  127. package/src/verax/detect/confidence-engine.js +100 -660
  128. package/src/verax/detect/confidence-helper.js +1 -0
  129. package/src/verax/detect/detection-engine.js +1 -18
  130. package/src/verax/detect/dynamic-route-findings.js +17 -14
  131. package/src/verax/detect/expectation-chain-detector.js +1 -1
  132. package/src/verax/detect/expectation-model.js +3 -5
  133. package/src/verax/detect/failure-cause-inference.js +293 -0
  134. package/src/verax/detect/findings-writer.js +126 -166
  135. package/src/verax/detect/flow-detector.js +2 -2
  136. package/src/verax/detect/form-silent-failure.js +98 -0
  137. package/src/verax/detect/index.js +51 -234
  138. package/src/verax/detect/invariants-enforcer.js +147 -0
  139. package/src/verax/detect/journey-stall-detector.js +4 -4
  140. package/src/verax/detect/navigation-silent-failure.js +82 -0
  141. package/src/verax/detect/problem-aggregator.js +361 -0
  142. package/src/verax/detect/route-findings.js +7 -6
  143. package/src/verax/detect/summary-writer.js +477 -0
  144. package/src/verax/detect/test-failure-cause-inference.js +314 -0
  145. package/src/verax/detect/ui-feedback-findings.js +18 -18
  146. package/src/verax/detect/verdict-engine.js +3 -57
  147. package/src/verax/detect/view-switch-correlator.js +2 -2
  148. package/src/verax/flow/flow-engine.js +2 -1
  149. package/src/verax/flow/flow-spec.js +0 -6
  150. package/src/verax/index.js +48 -412
  151. package/src/verax/intel/ts-program.js +1 -0
  152. package/src/verax/intel/vue-navigation-extractor.js +3 -0
  153. package/src/verax/learn/action-contract-extractor.js +67 -682
  154. package/src/verax/learn/ast-contract-extractor.js +1 -1
  155. package/src/verax/learn/flow-extractor.js +1 -0
  156. package/src/verax/learn/project-detector.js +5 -0
  157. package/src/verax/learn/react-router-extractor.js +2 -0
  158. package/src/verax/learn/route-validator.js +1 -4
  159. package/src/verax/learn/source-instrumenter.js +1 -0
  160. package/src/verax/learn/state-extractor.js +2 -1
  161. package/src/verax/learn/static-extractor.js +1 -0
  162. package/src/verax/observe/coverage-gaps.js +132 -0
  163. package/src/verax/observe/expectation-handler.js +126 -0
  164. package/src/verax/observe/incremental-skip.js +46 -0
  165. package/src/verax/observe/index.js +735 -84
  166. package/src/verax/observe/interaction-executor.js +192 -0
  167. package/src/verax/observe/interaction-runner.js +782 -530
  168. package/src/verax/observe/network-firewall.js +86 -0
  169. package/src/verax/observe/observation-builder.js +169 -0
  170. package/src/verax/observe/observe-context.js +1 -1
  171. package/src/verax/observe/observe-helpers.js +2 -1
  172. package/src/verax/observe/observe-runner.js +28 -24
  173. package/src/verax/observe/observers/budget-observer.js +3 -3
  174. package/src/verax/observe/observers/console-observer.js +4 -4
  175. package/src/verax/observe/observers/coverage-observer.js +4 -4
  176. package/src/verax/observe/observers/interaction-observer.js +3 -3
  177. package/src/verax/observe/observers/navigation-observer.js +4 -4
  178. package/src/verax/observe/observers/network-observer.js +4 -4
  179. package/src/verax/observe/observers/safety-observer.js +1 -1
  180. package/src/verax/observe/observers/ui-feedback-observer.js +4 -4
  181. package/src/verax/observe/page-traversal.js +138 -0
  182. package/src/verax/observe/snapshot-ops.js +94 -0
  183. package/src/verax/observe/ui-signal-sensor.js +2 -148
  184. package/src/verax/scan-summary-writer.js +10 -42
  185. package/src/verax/shared/artifact-manager.js +30 -13
  186. package/src/verax/shared/caching.js +1 -0
  187. package/src/verax/shared/expectation-tracker.js +1 -0
  188. package/src/verax/shared/zip-artifacts.js +6 -0
  189. package/src/verax/core/confidence-engine.js.backup +0 -471
  190. package/src/verax/shared/config-loader.js +0 -169
  191. /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
+ }
@@ -18,11 +18,11 @@ const traverse = _traverse.default || _traverse;
18
18
  * Detect useState patterns and their UI connections
19
19
  * PHASE 10: Enhanced with context tracking and AST source extraction
20
20
  * @param {string} content - File content
21
- * @param {string} filePath - Absolute file path
22
- * @param {string} relPath - Relative path from source root
21
+ * @param {string} _filePath - Absolute file path (unused)
22
+ * @param {string} _relPath - Relative path from source root (unused)
23
23
  * @returns {Array} Array of state-driven UI promises with context and AST source
24
24
  */
25
- export function detectUseStatePromises(content, filePath, relPath) {
25
+ export function detectUseStatePromises(content, _filePath, _relPath) {
26
26
  const promises = [];
27
27
  const lines = content.split('\n');
28
28
 
@@ -1,6 +1,17 @@
1
- import { writeFileSync, renameSync, mkdirSync } from 'fs';
1
+ import { writeFileSync, renameSync, mkdirSync, readFileSync as fsReadFileSync } from 'fs';
2
2
  import { dirname } from 'path';
3
3
 
4
+ /**
5
+ * Type-safe readFileSync wrapper for TypeScript
6
+ * Ensures string return type when encoding is specified
7
+ * @param {string} path - File path
8
+ * @param {string} encoding - Encoding (e.g., 'utf8', 'utf-8')
9
+ * @returns {string} File content as string
10
+ */
11
+ export function readFileSync(path, encoding) {
12
+ return String(fsReadFileSync(path, encoding));
13
+ }
14
+
4
15
  /**
5
16
  * Atomic write for JSON files
6
17
  * Writes to a temp file and renames to prevent partial writes
@@ -0,0 +1,72 @@
1
+ /**
2
+ *
3
+ * Formats and prints standardized CLI output for VERAX runs.
4
+ *
5
+ * Output format (decision-oriented, minimal):
6
+ * - ✔ VERAX finished
7
+ * - ✔ 14 interactions tested
8
+ * - ✖ 3 user problems found (2 HIGH, 1 MEDIUM)
9
+ * - → Open .verax/SUMMARY.md
10
+ */
11
+
12
+ /**
13
+ * Format console output for a completed run
14
+ *
15
+ * @param {Object} stats - Run statistics
16
+ * @param {number} stats.flowsScanned - Public flows scanned
17
+ * @param {number} stats.silentFailures - Silent failures detected
18
+ * @param {string} stats.outDir - Output directory path
19
+ * @returns {string} Formatted output (multiple lines)
20
+ */
21
+ export function formatConsoleOutput(stats) {
22
+ const lines = [];
23
+
24
+ const flowsScanned = stats?.flowsScanned || 0;
25
+ const silentFailures = stats?.silentFailures || 0;
26
+ const outDir = stats?.outDir || '.verax';
27
+
28
+ // Line 1: Scan complete summary
29
+ const flowsStr = `${flowsScanned} flow${flowsScanned !== 1 ? 's' : ''}`;
30
+ const failuresStr = `${silentFailures} silent failure${silentFailures !== 1 ? 's' : ''}`;
31
+ lines.push(`✓ Scan complete — ${flowsStr} checked, ${failuresStr} found`);
32
+
33
+ // Line 2: Point to SUMMARY.md
34
+ lines.push(`→ See ${outDir}/SUMMARY.md for details`);
35
+
36
+ // Line 3: Mention output directory
37
+ lines.push('');
38
+ lines.push(`Output saved to ${outDir}/`);
39
+ lines.push(` • SUMMARY.md — Human-readable findings`);
40
+ lines.push(` • REPORT.json — Machine-readable results`);
41
+
42
+ return lines.join('\n');
43
+ }
44
+
45
+ /**
46
+ * Print formatted console output to stdout
47
+ */
48
+ export function printConsoleOutput(stats) {
49
+ const output = formatConsoleOutput(stats);
50
+ console.log(output);
51
+ return output;
52
+ }
53
+
54
+ /**
55
+ * Format and print error output when fatal error occurs
56
+ *
57
+ * @param {string} message - Error message
58
+ * @param {boolean} [_debug] - If true, include more details; if false, minimal
59
+ */
60
+ export function formatErrorOutput(message, _debug = false) {
61
+ // Single-line error message with [ERROR] prefix
62
+ return `[ERROR] ${message}`;
63
+ }
64
+
65
+ /**
66
+ * Print error output
67
+ */
68
+ export function printErrorOutput(message, _debug = false) {
69
+ const output = formatErrorOutput(message, _debug);
70
+ console.error(output);
71
+ return output;
72
+ }