@veraxhq/verax 0.1.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 (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +237 -0
  3. package/bin/verax.js +452 -0
  4. package/package.json +57 -0
  5. package/src/verax/detect/comparison.js +69 -0
  6. package/src/verax/detect/confidence-engine.js +498 -0
  7. package/src/verax/detect/evidence-validator.js +33 -0
  8. package/src/verax/detect/expectation-model.js +204 -0
  9. package/src/verax/detect/findings-writer.js +31 -0
  10. package/src/verax/detect/index.js +397 -0
  11. package/src/verax/detect/skip-classifier.js +202 -0
  12. package/src/verax/flow/flow-engine.js +265 -0
  13. package/src/verax/flow/flow-spec.js +145 -0
  14. package/src/verax/flow/redaction.js +74 -0
  15. package/src/verax/index.js +97 -0
  16. package/src/verax/learn/action-contract-extractor.js +281 -0
  17. package/src/verax/learn/ast-contract-extractor.js +255 -0
  18. package/src/verax/learn/index.js +18 -0
  19. package/src/verax/learn/manifest-writer.js +97 -0
  20. package/src/verax/learn/project-detector.js +87 -0
  21. package/src/verax/learn/react-router-extractor.js +73 -0
  22. package/src/verax/learn/route-extractor.js +122 -0
  23. package/src/verax/learn/route-validator.js +215 -0
  24. package/src/verax/learn/source-instrumenter.js +214 -0
  25. package/src/verax/learn/static-extractor.js +222 -0
  26. package/src/verax/learn/truth-assessor.js +96 -0
  27. package/src/verax/learn/ts-contract-resolver.js +395 -0
  28. package/src/verax/observe/browser.js +22 -0
  29. package/src/verax/observe/console-sensor.js +166 -0
  30. package/src/verax/observe/dom-signature.js +23 -0
  31. package/src/verax/observe/domain-boundary.js +38 -0
  32. package/src/verax/observe/evidence-capture.js +5 -0
  33. package/src/verax/observe/human-driver.js +376 -0
  34. package/src/verax/observe/index.js +67 -0
  35. package/src/verax/observe/interaction-discovery.js +269 -0
  36. package/src/verax/observe/interaction-runner.js +410 -0
  37. package/src/verax/observe/network-sensor.js +173 -0
  38. package/src/verax/observe/selector-generator.js +74 -0
  39. package/src/verax/observe/settle.js +155 -0
  40. package/src/verax/observe/state-ui-sensor.js +200 -0
  41. package/src/verax/observe/traces-writer.js +82 -0
  42. package/src/verax/observe/ui-signal-sensor.js +197 -0
  43. package/src/verax/resolve-workspace-root.js +173 -0
  44. package/src/verax/scan-summary-writer.js +41 -0
  45. package/src/verax/shared/artifact-manager.js +139 -0
  46. package/src/verax/shared/caching.js +104 -0
  47. package/src/verax/shared/expectation-proof.js +4 -0
  48. package/src/verax/shared/redaction.js +227 -0
  49. package/src/verax/shared/retry-policy.js +89 -0
  50. package/src/verax/shared/timing-metrics.js +44 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ # MIT License
2
+
3
+ Copyright (c) 2025 VERAX
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,237 @@
1
+ 🛡️ VERAX
2
+
3
+ VERAX detects silent user failures by comparing what your code promises with what users actually experience.
4
+
5
+ Silent failures don’t crash your site.
6
+ They don’t show errors.
7
+ They simply lose users quietly.
8
+
9
+ VERAX exists to surface those failures — with evidence, not guesses.
10
+
11
+ 🤔 What is VERAX?
12
+
13
+ A silent user failure happens when your code clearly implies that something should happen —
14
+ but from the user’s point of view, nothing meaningful does.
15
+
16
+ Examples:
17
+
18
+ A button click that should navigate… but doesn’t.
19
+
20
+ A form submission that triggers an API call… but shows no feedback.
21
+
22
+ A state update that runs in code… but never reaches the UI.
23
+
24
+ These issues are frustrating for users and difficult for teams to catch.
25
+
26
+ VERAX reads your source code to understand what should happen, then opens your website in a real browser and experiences it like a human user.
27
+ When code expectations and user reality don’t match, VERAX reports the gap — clearly and honestly.
28
+
29
+ VERAX does not guess intent.
30
+ It only reports failures that your code explicitly promises.
31
+
32
+ ✅ What VERAX does (today)
33
+
34
+ 🔍 Detects silent user failures by comparing code-derived expectations with real browser behavior
35
+
36
+ 🧠 Extracts expectations from source code using static analysis:
37
+
38
+ Navigation from HTML links and React Router / Next.js routes
39
+
40
+ Network actions from fetch / axios calls with static URLs
41
+
42
+ State mutations from React useState, Redux dispatch, and Zustand set
43
+
44
+ 🖱️ Observes websites like a real user using Playwright (clicks, forms, navigation, scrolling)
45
+
46
+ 📊 Assigns confidence levels (HIGH / MEDIUM / LOW) based on evidence strength
47
+
48
+ 🧾 Provides clear evidence for every finding:
49
+
50
+ Screenshots
51
+
52
+ Network activity
53
+
54
+ Console errors
55
+
56
+ DOM and state changes
57
+
58
+ 💻 Runs as a CLI tool with verax scan and verax flow
59
+
60
+ 🧱 Supports real-world projects:
61
+
62
+ Static HTML sites
63
+
64
+ React SPAs
65
+
66
+ Next.js (App Router & Pages Router)
67
+
68
+ 🔐 Protects privacy by automatically redacting secrets and sensitive data
69
+
70
+ 🚫 What VERAX does NOT do
71
+
72
+ ❌ Does not guess intent — no heuristics, no assumptions
73
+
74
+ ❌ Does not support dynamic routes (/user/${id} is intentionally skipped)
75
+
76
+ ❌ Does not replace QA or tests — it complements them
77
+
78
+ ❌ Does not monitor production traffic
79
+
80
+ ❌ Does not work for every framework
81
+
82
+ ❌ Does not detect every bug — only silent failures backed by code promises
83
+
84
+ ❌ Does not use AI — all results are deterministic and explainable
85
+
86
+ 🔄 How VERAX works (high-level)
87
+
88
+ VERAX runs three phases automatically:
89
+
90
+ 1️⃣ Learn
91
+
92
+ Analyzes your source code to understand:
93
+
94
+ Project structure
95
+
96
+ Routes
97
+
98
+ What interactions promise to do
99
+ Creates a manifest of expectations derived directly from code.
100
+
101
+ 2️⃣ Observe
102
+
103
+ Opens your site in a real browser and interacts naturally:
104
+
105
+ Clicks buttons
106
+
107
+ Fills forms
108
+
109
+ Follows links
110
+ Records what actually happens.
111
+
112
+ 3️⃣ Detect
113
+
114
+ Compares code expectations with real behavior.
115
+ Reports a finding only when code promised an outcome that did not occur — with evidence and confidence.
116
+
117
+ You run one command. VERAX handles the rest.
118
+
119
+ 📦 Installation
120
+
121
+ Requirements: Node.js 18+
122
+
123
+ From npm (when published):
124
+ npm install -g @verax/verax
125
+
126
+ From source:
127
+ git clone <repository-url>
128
+ cd odavlguardian
129
+ npm install
130
+ npm link
131
+
132
+ 🚀 Basic usage
133
+ Scan a website:
134
+ verax scan --url <http://localhost:3000>
135
+
136
+ JSON output (CI-friendly):
137
+ verax scan --url <http://localhost:3000> --json
138
+
139
+ Scan a specific project directory:
140
+ verax scan --url <http://localhost:3000> --projectRoot ./my-website
141
+
142
+ Execute a user flow:
143
+ verax flow --flow ./flows/login.json --url <http://localhost:3000>
144
+
145
+ Outputs
146
+
147
+ Artifacts are written to:
148
+
149
+ .verax/runs/<runId>/
150
+
151
+ Includes:
152
+
153
+ summary.json — overall results
154
+
155
+ findings.json — detected issues with evidence
156
+
157
+ traces.jsonl — interaction traces
158
+
159
+ evidence/ — screenshots and logs
160
+
161
+ Exit codes
162
+
163
+ 0 — No HIGH confidence findings
164
+
165
+ 1 — MEDIUM / LOW findings only
166
+
167
+ 2 — At least one HIGH confidence finding
168
+
169
+ 3 — Fatal error
170
+
171
+ 📊 Understanding results
172
+
173
+ Each finding includes:
174
+
175
+ Type — what kind of failure occurred
176
+
177
+ Reason — plain English explanation
178
+
179
+ Confidence — score (0–100) + level
180
+
181
+ Evidence — screenshots, network data, console logs
182
+
183
+ Confidence levels:
184
+
185
+ HIGH (80–100) — strong, unambiguous evidence
186
+
187
+ MEDIUM (60–79) — likely failure with some ambiguity
188
+
189
+ LOW (<60) — weak or partial evidence
190
+
191
+ VERAX prefers missing an issue over reporting a false one.
192
+
193
+ 🔐 Safety and privacy
194
+
195
+ 🔒 No guessing
196
+
197
+ 🧹 Automatic redaction of secrets and personal data
198
+
199
+ 🛑 Safe by default — blocks destructive actions unless explicitly allowed
200
+
201
+ 🧭 Flows are opt-in only — nothing runs without your definition
202
+
203
+ 🎯 When VERAX is a good fit
204
+
205
+ Marketing and public-facing websites
206
+
207
+ SaaS signup and pricing flows
208
+
209
+ React and Next.js projects
210
+
211
+ CI pipelines that need UX validation
212
+
213
+ Teams that value evidence over assumptions
214
+
215
+ 🚫 When VERAX is NOT a good fit
216
+
217
+ Internal admin dashboards
218
+
219
+ Authentication-heavy systems
220
+
221
+ Apps built around highly dynamic routing
222
+
223
+ Unsupported frameworks
224
+
225
+ Teams expecting a full QA replacement
226
+
227
+ 🧪 Project status
228
+
229
+ VERAX is a production-grade CLI tool in active development.
230
+ It is designed for early adopters and technical teams.
231
+
232
+ VERAX is not a SaaS product.
233
+ It runs locally or in CI. There is no hosted service.
234
+
235
+ 📄 License
236
+
237
+ MIT
package/bin/verax.js ADDED
@@ -0,0 +1,452 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname, resolve } from 'path';
5
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, appendFileSync } from 'fs';
6
+ import inquirer from 'inquirer';
7
+ import { learn } from '../src/verax/index.js';
8
+ import { resolveWorkspaceRoot } from '../src/verax/resolve-workspace-root.js';
9
+ import { initArtifactPaths, writeSummary, writeFindings, appendTrace } from '../src/verax/shared/artifact-manager.js';
10
+ import { redactFinding, redactTrace } from '../src/verax/shared/redaction.js';
11
+ import { initMetrics, recordMetric, getMetrics } from '../src/verax/shared/timing-metrics.js';
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = dirname(__filename);
15
+
16
+ async function main() {
17
+ const args = process.argv.slice(2);
18
+ const command = args[0];
19
+
20
+ // Handle Wave 9 CLI commands
21
+ if (command === 'scan') {
22
+ await handleScan(args.slice(1));
23
+ return;
24
+ }
25
+
26
+ if (command === 'flow') {
27
+ await handleFlow(args.slice(1));
28
+ return;
29
+ }
30
+
31
+ if (['--help', '-h', 'help'].includes(command)) {
32
+ printHelp();
33
+ process.exit(0);
34
+ }
35
+
36
+ // Default: interactive mode (existing behavior)
37
+ console.log('VERAX\n');
38
+
39
+ // Reuse parsed args from above for optional flags
40
+ let projectDir = null;
41
+ let url = null;
42
+ let manifestPath = null;
43
+
44
+ const projectDirIndex = args.indexOf('--project-dir');
45
+ const projectDirArg = projectDirIndex !== -1 && args[projectDirIndex + 1] ? args[projectDirIndex + 1] : null;
46
+
47
+ const urlIndex = args.indexOf('--url');
48
+ if (urlIndex !== -1 && urlIndex + 1 < args.length) {
49
+ url = args[urlIndex + 1];
50
+ }
51
+
52
+ const manifestIndex = args.indexOf('--manifest');
53
+ if (manifestIndex !== -1 && manifestIndex + 1 < args.length) {
54
+ manifestPath = resolve(args[manifestIndex + 1]);
55
+ }
56
+
57
+ // Resolve workspace root using the new function
58
+ try {
59
+ const resolved = resolveWorkspaceRoot(projectDirArg, process.cwd());
60
+ projectDir = resolved.workspaceRoot;
61
+
62
+ if (resolved.isRepoRoot) {
63
+ console.error(
64
+ 'VERAX: Refusing to write artifacts in repository root.\n' +
65
+ 'Use --project-dir to specify the target project directory.'
66
+ );
67
+ process.exit(2);
68
+ }
69
+ } catch (error) {
70
+ console.error(`Error: ${error.message}`);
71
+ process.exit(2);
72
+ }
73
+
74
+ const actions = ['Scan my website'];
75
+ let action;
76
+
77
+ if (actions.length === 1) {
78
+ action = actions[0];
79
+ } else {
80
+ const result = await inquirer.prompt([
81
+ {
82
+ type: 'list',
83
+ name: 'action',
84
+ message: 'What would you like to do?',
85
+ choices: actions,
86
+ default: actions[0]
87
+ }
88
+ ]);
89
+ action = result.action;
90
+ }
91
+
92
+ if (action === 'Scan my website') {
93
+ try {
94
+ console.log('Understanding your website...');
95
+ const manifest = await learn(projectDir);
96
+
97
+ console.log(`\nProject type: ${manifest.projectType}`);
98
+ console.log(`Total routes: ${manifest.routes.length}`);
99
+ console.log(`Public routes: ${manifest.publicRoutes.length}`);
100
+ console.log(`Internal routes: ${manifest.internalRoutes.length}`);
101
+
102
+ if (url) {
103
+ const { scan } = await import('../src/verax/index.js');
104
+ const result = await scan(projectDir, url, manifestPath);
105
+ const { observation, findings, scanSummary } = result;
106
+
107
+ console.log('\nObserving real user interactions...');
108
+ console.log('Comparing expectations with reality...');
109
+
110
+ console.log('\nScan complete.\n');
111
+ console.log(`Total interactions observed: ${observation.traces.length}`);
112
+ console.log(`Silent failures detected: ${findings.findings.length}`);
113
+
114
+ if (findings.findings.length > 0) {
115
+ console.log(`\nFindings report: ${findings.findingsPath}\n`);
116
+ } else {
117
+ console.log('\nNo silent user failures were detected.');
118
+ console.log(`\nFindings report: ${findings.findingsPath}\n`);
119
+ }
120
+
121
+ if (scanSummary) {
122
+ const truth = scanSummary.truth;
123
+ console.log('Truth Summary:');
124
+ console.log(`- Learn: routes=${truth.learn.routesDiscovered} (confidence: ${truth.learn.routesConfidence}, source: ${truth.learn.routesSource}), expectations=${truth.learn.expectationsDiscovered} (strong=${truth.learn.expectationsStrong}, weak=${truth.learn.expectationsWeak})`);
125
+ if (truth.learn.validation) {
126
+ console.log(`- Learn validation: validated=${truth.learn.validation.routesValidated}, reachable=${truth.learn.validation.routesReachable}, unreachable=${truth.learn.validation.routesUnreachable}`);
127
+ }
128
+ const coverage = truth.observe.coverage;
129
+ const coverageLine = coverage
130
+ ? `Coverage: selected=${coverage.candidatesSelected}/${coverage.candidatesDiscovered} (cap=${coverage.cap})${coverage.capped ? ' — capped' : ''}`
131
+ : null;
132
+ console.log(`- Observe: interactions=${truth.observe.interactionsObserved}, external-blocked=${truth.observe.externalNavigationBlockedCount}, timeouts=${truth.observe.timeoutsCount}`);
133
+ if (coverageLine) {
134
+ console.log(` - ${coverageLine}`);
135
+ }
136
+ console.log(`- Detect: analyzed=${truth.detect.interactionsAnalyzed}, skipped(no expectation)=${truth.detect.interactionsSkippedNoExpectation}, findings=${truth.detect.findingsCount}`);
137
+ if (truth.detect.skips && truth.detect.skips.total > 0) {
138
+ const topReasons = truth.detect.skips.reasons.slice(0, 3).map(r => `${r.code}=${r.count}`).join(', ');
139
+ console.log(`- Detect skips: ${truth.detect.skips.total} (top: ${topReasons})`);
140
+ }
141
+ console.log(`- Scan summary: ${scanSummary.summaryPath}\n`);
142
+ }
143
+ } else {
144
+ console.log('\nNote: Provide --url to observe website interactions\n');
145
+ }
146
+
147
+ process.exit(0);
148
+ } catch (error) {
149
+ console.error(`\nError: ${error.message}`);
150
+ if (error.stack && process.env.DEBUG) {
151
+ console.error(error.stack);
152
+ }
153
+ process.exit(2);
154
+ }
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Parse command-line arguments into key-value object
160
+ */
161
+ function parseArgs(argArray) {
162
+ const opts = {};
163
+ for (let i = 0; i < argArray.length; i++) {
164
+ if (argArray[i].startsWith('--')) {
165
+ const key = argArray[i].substring(2);
166
+ if (i + 1 < argArray.length && !argArray[i + 1].startsWith('--')) {
167
+ opts[key] = argArray[i + 1];
168
+ i++;
169
+ } else {
170
+ opts[key] = true;
171
+ }
172
+ }
173
+ }
174
+ return opts;
175
+ }
176
+
177
+ /**
178
+ * Print help message
179
+ */
180
+ function printHelp() {
181
+ console.log(`
182
+ VERAX — Web Application Verification Platform
183
+
184
+ Usage:
185
+ verax [interactive] Interactive mode
186
+ verax scan --url <url> [--projectRoot <path>] [--json] [--out <dir>]
187
+ verax flow --flow <path> [--url <url>] [--json] [--out <dir>]
188
+ verax --help Show this message
189
+
190
+ Commands:
191
+ scan Run full scan on a live URL
192
+ flow Execute a user flow against a URL
193
+
194
+ Options:
195
+ --url <url> Target URL
196
+ --flow <path> Path to flow definition file
197
+ --projectRoot <p> Project root directory (defaults to cwd)
198
+ --json Output machine-readable JSON
199
+ --out <dir> Output directory (defaults to .verax/runs)
200
+ --help, -h Show this help
201
+
202
+ Exit Codes:
203
+ 0 No HIGH findings
204
+ 1 MEDIUM/LOW findings only
205
+ 2 At least one HIGH finding
206
+ 3 Fatal error
207
+ `);
208
+ }
209
+
210
+ /**
211
+ * Handle 'verax scan' command - integrated with main pipeline
212
+ */
213
+ async function handleScan(scanArgs) {
214
+ const opts = parseArgs(scanArgs);
215
+
216
+ if (!opts.url) {
217
+ console.error('Error: --url is required');
218
+ process.exit(3);
219
+ }
220
+
221
+ const projectRoot = opts.projectRoot || process.cwd();
222
+ const url = opts.url;
223
+ const jsonOutput = opts.json === true || opts.json === 'true';
224
+ const outDir = opts.out;
225
+
226
+ try {
227
+ initMetrics();
228
+ const startTime = Date.now();
229
+
230
+ // Initialize artifact paths
231
+ const artifactPaths = outDir
232
+ ? { runDir: outDir, summary: `${outDir}/summary.json`, findings: `${outDir}/findings.json`, traces: `${outDir}/traces.jsonl` }
233
+ : initArtifactPaths(projectRoot);
234
+
235
+ // Ensure artifact directories exist
236
+ mkdirSync(artifactPaths.runDir, { recursive: true });
237
+
238
+ // Run the full VERAX pipeline using the scan() orchestrator
239
+ console.error(`[VERAX] Starting scan for ${url}`);
240
+ const resolvedRootResult = await resolveWorkspaceRoot(projectRoot);
241
+ const resolvedRoot = resolvedRootResult.workspaceRoot;
242
+
243
+ const scanStart = Date.now();
244
+ const { scan } = await import('../src/verax/index.js');
245
+ const result = await scan(resolvedRoot, url, null, artifactPaths.runId);
246
+ recordMetric('totalMs', Date.now() - scanStart);
247
+
248
+ const manifest = result.manifest;
249
+ const observation = result.observation;
250
+ const findings = result.findings;
251
+
252
+ const metrics = getMetrics();
253
+ metrics.totalMs = Date.now() - startTime;
254
+
255
+ // Count findings by level
256
+ const findingsList = findings?.findings || [];
257
+ const findingsCounts = {
258
+ HIGH: findingsList.filter(f => f.confidence?.level === 'HIGH').length,
259
+ MEDIUM: findingsList.filter(f => f.confidence?.level === 'MEDIUM').length,
260
+ LOW: findingsList.filter(f => f.confidence?.level === 'LOW').length,
261
+ UNKNOWN: findingsList.filter(f => !f.confidence?.level || f.confidence?.level === 'UNKNOWN').length
262
+ };
263
+
264
+ // Get top findings
265
+ const topFindings = findingsList
266
+ .slice(0, 3)
267
+ .map(f => ({
268
+ type: f.type,
269
+ reason: f.reason,
270
+ confidence: f.confidence?.score || 0
271
+ }));
272
+
273
+ // Write summary with metrics to artifact paths
274
+ writeSummary(artifactPaths, {
275
+ url,
276
+ projectRoot: resolvedRoot,
277
+ metrics,
278
+ findingsCounts,
279
+ topFindings
280
+ });
281
+
282
+ // Determine exit code
283
+ const HIGH_COUNT = findingsCounts.HIGH;
284
+ const hasAny = Object.values(findingsCounts).reduce((a, b) => a + b, 0) > 0;
285
+
286
+ if (jsonOutput) {
287
+ const summary = {
288
+ runId: artifactPaths.runId,
289
+ url,
290
+ metrics,
291
+ findingsCounts,
292
+ topFindings,
293
+ total: findingsList.length
294
+ };
295
+ console.log(JSON.stringify(summary, null, 2));
296
+ } else {
297
+ console.error(`[VERAX] Scan complete in ${(metrics.totalMs / 1000).toFixed(2)}s`);
298
+ console.error(`[VERAX] Findings: HIGH=${HIGH_COUNT}, MEDIUM=${findingsCounts.MEDIUM}, LOW=${findingsCounts.LOW}`);
299
+ if (topFindings.length > 0) {
300
+ console.error(`[VERAX] Top findings:`);
301
+ topFindings.forEach(f => {
302
+ console.error(` - [${f.confidence}%] ${f.type}: ${f.reason}`);
303
+ });
304
+ }
305
+ }
306
+
307
+ // Exit with appropriate code
308
+ if (HIGH_COUNT > 0) {
309
+ process.exit(2);
310
+ } else if (hasAny) {
311
+ process.exit(1);
312
+ } else {
313
+ process.exit(0);
314
+ }
315
+ } catch (error) {
316
+ if (!jsonOutput) {
317
+ console.error(`[VERAX] Scan failed: ${error.message}`);
318
+ }
319
+ process.exit(3);
320
+ }
321
+ }
322
+
323
+ /**
324
+ * Handle 'verax flow' command - execute a user flow
325
+ */
326
+ async function handleFlow(flowArgs) {
327
+ const opts = parseArgs(flowArgs);
328
+
329
+ if (!opts.flow) {
330
+ console.error('Error: --flow is required');
331
+ process.exit(3);
332
+ }
333
+
334
+ const flowPath = resolve(opts.flow);
335
+ if (!existsSync(flowPath)) {
336
+ console.error(`Error: Flow file not found: ${flowPath}`);
337
+ process.exit(3);
338
+ }
339
+
340
+ const url = opts.url;
341
+
342
+ const jsonOutput = opts.json === true || opts.json === 'true';
343
+ const projectRoot = opts.projectRoot || process.cwd();
344
+ const outDir = opts.out;
345
+
346
+ try {
347
+ initMetrics();
348
+ const startTime = Date.now();
349
+
350
+ // Initialize artifact paths
351
+ const artifactPaths = outDir
352
+ ? { runDir: outDir, flows: `${outDir}/flows` }
353
+ : initArtifactPaths(projectRoot);
354
+ mkdirSync(artifactPaths.runDir, { recursive: true });
355
+ if (artifactPaths.flows) {
356
+ mkdirSync(artifactPaths.flows, { recursive: true });
357
+ }
358
+
359
+ // Load and validate flow spec
360
+ const { readFileSync } = await import('fs');
361
+ const flowContent = readFileSync(flowPath, 'utf-8');
362
+ const flowSpec = JSON.parse(flowContent);
363
+
364
+ // Override baseUrl with --url if provided
365
+ if (url) {
366
+ flowSpec.baseUrl = url;
367
+ }
368
+
369
+ const { validateFlowSpec } = await import('../src/verax/flow/flow-spec.js');
370
+ const validatedSpec = validateFlowSpec(flowSpec);
371
+
372
+ // Use baseUrl from spec for navigation
373
+ const targetUrl = validatedSpec.baseUrl;
374
+
375
+ // Create browser and sensors
376
+ const { createBrowser, navigateToUrl, closeBrowser } = await import('../src/verax/observe/browser.js');
377
+ const { NetworkSensor } = await import('../src/verax/observe/network-sensor.js');
378
+ const { ConsoleSensor } = await import('../src/verax/observe/console-sensor.js');
379
+ const { UISignalSensor } = await import('../src/verax/observe/ui-signal-sensor.js');
380
+
381
+ const { browser, page } = await createBrowser();
382
+ const sensors = {
383
+ network: new NetworkSensor(),
384
+ console: new ConsoleSensor(),
385
+ uiSignals: new UISignalSensor()
386
+ };
387
+
388
+ try {
389
+ await navigateToUrl(page, targetUrl);
390
+
391
+ // Execute flow
392
+ const { executeFlow } = await import('../src/verax/flow/flow-engine.js');
393
+ const flowResult = await executeFlow(page, validatedSpec, sensors);
394
+
395
+ await closeBrowser(browser);
396
+
397
+ recordMetric('totalMs', Date.now() - startTime);
398
+ const metrics = getMetrics();
399
+
400
+ // Write flow results
401
+ const flowResultsPath = resolve(artifactPaths.flows || artifactPaths.runDir, 'flow-results.json');
402
+ writeFileSync(flowResultsPath, JSON.stringify({
403
+ flow: validatedSpec.name,
404
+ url: targetUrl,
405
+ success: flowResult.success,
406
+ findings: flowResult.findings,
407
+ stepResults: flowResult.stepResults,
408
+ metrics
409
+ }, null, 2) + '\n');
410
+
411
+ if (jsonOutput) {
412
+ console.log(JSON.stringify({
413
+ flow: validatedSpec.name,
414
+ url: targetUrl,
415
+ success: flowResult.success,
416
+ findingsCount: flowResult.findings.length,
417
+ metrics
418
+ }, null, 2));
419
+ } else {
420
+ console.error(`[VERAX] Flow execution complete in ${(metrics.totalMs / 1000).toFixed(2)}s`);
421
+ console.error(`[VERAX] Flow: ${validatedSpec.name}`);
422
+ console.error(`[VERAX] Success: ${flowResult.success}`);
423
+ console.error(`[VERAX] Findings: ${flowResult.findings.length}`);
424
+ if (flowResult.findings.length > 0) {
425
+ flowResult.findings.forEach((f, i) => {
426
+ console.error(` ${i + 1}. [Step ${f.stepIndex}] ${f.type}: ${f.reason}`);
427
+ });
428
+ }
429
+ }
430
+
431
+ // Exit code: 0 if success, 2 if findings, 3 if error
432
+ if (!flowResult.success) {
433
+ process.exit(2);
434
+ } else {
435
+ process.exit(0);
436
+ }
437
+ } catch (error) {
438
+ await closeBrowser(browser);
439
+ throw error;
440
+ }
441
+ } catch (error) {
442
+ if (!jsonOutput) {
443
+ console.error(`[VERAX] Flow execution failed: ${error.message}`);
444
+ }
445
+ process.exit(3);
446
+ }
447
+ }
448
+
449
+ main().catch((error) => {
450
+ console.error(`Fatal error: ${error.message}`);
451
+ process.exit(2);
452
+ });