@veraxhq/verax 0.1.0 → 0.2.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 (126) hide show
  1. package/README.md +123 -88
  2. package/bin/verax.js +11 -452
  3. package/package.json +14 -36
  4. package/src/cli/commands/default.js +523 -0
  5. package/src/cli/commands/doctor.js +165 -0
  6. package/src/cli/commands/inspect.js +109 -0
  7. package/src/cli/commands/run.js +402 -0
  8. package/src/cli/entry.js +196 -0
  9. package/src/cli/util/atomic-write.js +37 -0
  10. package/src/cli/util/detection-engine.js +296 -0
  11. package/src/cli/util/env-url.js +33 -0
  12. package/src/cli/util/errors.js +44 -0
  13. package/src/cli/util/events.js +34 -0
  14. package/src/cli/util/expectation-extractor.js +378 -0
  15. package/src/cli/util/findings-writer.js +31 -0
  16. package/src/cli/util/idgen.js +87 -0
  17. package/src/cli/util/learn-writer.js +39 -0
  18. package/src/cli/util/observation-engine.js +366 -0
  19. package/src/cli/util/observe-writer.js +25 -0
  20. package/src/cli/util/paths.js +29 -0
  21. package/src/cli/util/project-discovery.js +277 -0
  22. package/src/cli/util/project-writer.js +26 -0
  23. package/src/cli/util/redact.js +128 -0
  24. package/src/cli/util/run-id.js +30 -0
  25. package/src/cli/util/summary-writer.js +32 -0
  26. package/src/verax/cli/ci-summary.js +35 -0
  27. package/src/verax/cli/context-explanation.js +89 -0
  28. package/src/verax/cli/doctor.js +277 -0
  29. package/src/verax/cli/error-normalizer.js +154 -0
  30. package/src/verax/cli/explain-output.js +105 -0
  31. package/src/verax/cli/finding-explainer.js +130 -0
  32. package/src/verax/cli/init.js +237 -0
  33. package/src/verax/cli/run-overview.js +163 -0
  34. package/src/verax/cli/url-safety.js +101 -0
  35. package/src/verax/cli/wizard.js +98 -0
  36. package/src/verax/cli/zero-findings-explainer.js +57 -0
  37. package/src/verax/cli/zero-interaction-explainer.js +127 -0
  38. package/src/verax/core/action-classifier.js +86 -0
  39. package/src/verax/core/budget-engine.js +218 -0
  40. package/src/verax/core/canonical-outcomes.js +157 -0
  41. package/src/verax/core/decision-snapshot.js +335 -0
  42. package/src/verax/core/determinism-model.js +403 -0
  43. package/src/verax/core/incremental-store.js +237 -0
  44. package/src/verax/core/invariants.js +356 -0
  45. package/src/verax/core/promise-model.js +230 -0
  46. package/src/verax/core/replay-validator.js +350 -0
  47. package/src/verax/core/replay.js +222 -0
  48. package/src/verax/core/run-id.js +175 -0
  49. package/src/verax/core/run-manifest.js +99 -0
  50. package/src/verax/core/silence-impact.js +369 -0
  51. package/src/verax/core/silence-model.js +521 -0
  52. package/src/verax/detect/comparison.js +2 -34
  53. package/src/verax/detect/confidence-engine.js +764 -329
  54. package/src/verax/detect/detection-engine.js +293 -0
  55. package/src/verax/detect/evidence-index.js +177 -0
  56. package/src/verax/detect/expectation-model.js +194 -172
  57. package/src/verax/detect/explanation-helpers.js +187 -0
  58. package/src/verax/detect/finding-detector.js +450 -0
  59. package/src/verax/detect/findings-writer.js +44 -8
  60. package/src/verax/detect/flow-detector.js +366 -0
  61. package/src/verax/detect/index.js +172 -286
  62. package/src/verax/detect/interactive-findings.js +613 -0
  63. package/src/verax/detect/signal-mapper.js +308 -0
  64. package/src/verax/detect/verdict-engine.js +563 -0
  65. package/src/verax/evidence-index-writer.js +61 -0
  66. package/src/verax/index.js +90 -14
  67. package/src/verax/intel/effect-detector.js +368 -0
  68. package/src/verax/intel/handler-mapper.js +249 -0
  69. package/src/verax/intel/index.js +281 -0
  70. package/src/verax/intel/route-extractor.js +280 -0
  71. package/src/verax/intel/ts-program.js +256 -0
  72. package/src/verax/intel/vue-navigation-extractor.js +579 -0
  73. package/src/verax/intel/vue-router-extractor.js +323 -0
  74. package/src/verax/learn/action-contract-extractor.js +335 -101
  75. package/src/verax/learn/ast-contract-extractor.js +95 -5
  76. package/src/verax/learn/flow-extractor.js +172 -0
  77. package/src/verax/learn/manifest-writer.js +97 -47
  78. package/src/verax/learn/project-detector.js +40 -0
  79. package/src/verax/learn/route-extractor.js +27 -96
  80. package/src/verax/learn/state-extractor.js +212 -0
  81. package/src/verax/learn/static-extractor-navigation.js +114 -0
  82. package/src/verax/learn/static-extractor-validation.js +88 -0
  83. package/src/verax/learn/static-extractor.js +112 -4
  84. package/src/verax/learn/truth-assessor.js +24 -21
  85. package/src/verax/observe/aria-sensor.js +211 -0
  86. package/src/verax/observe/browser.js +10 -5
  87. package/src/verax/observe/console-sensor.js +1 -17
  88. package/src/verax/observe/domain-boundary.js +10 -1
  89. package/src/verax/observe/expectation-executor.js +512 -0
  90. package/src/verax/observe/flow-matcher.js +143 -0
  91. package/src/verax/observe/focus-sensor.js +196 -0
  92. package/src/verax/observe/human-driver.js +643 -275
  93. package/src/verax/observe/index.js +908 -27
  94. package/src/verax/observe/index.js.backup +1 -0
  95. package/src/verax/observe/interaction-discovery.js +365 -14
  96. package/src/verax/observe/interaction-runner.js +563 -198
  97. package/src/verax/observe/loading-sensor.js +139 -0
  98. package/src/verax/observe/navigation-sensor.js +255 -0
  99. package/src/verax/observe/network-sensor.js +55 -7
  100. package/src/verax/observe/observed-expectation-deriver.js +186 -0
  101. package/src/verax/observe/observed-expectation.js +305 -0
  102. package/src/verax/observe/page-frontier.js +234 -0
  103. package/src/verax/observe/settle.js +37 -17
  104. package/src/verax/observe/state-sensor.js +389 -0
  105. package/src/verax/observe/timing-sensor.js +228 -0
  106. package/src/verax/observe/traces-writer.js +61 -20
  107. package/src/verax/observe/ui-signal-sensor.js +136 -17
  108. package/src/verax/scan-summary-writer.js +77 -15
  109. package/src/verax/shared/artifact-manager.js +110 -8
  110. package/src/verax/shared/budget-profiles.js +136 -0
  111. package/src/verax/shared/ci-detection.js +39 -0
  112. package/src/verax/shared/config-loader.js +170 -0
  113. package/src/verax/shared/dynamic-route-utils.js +218 -0
  114. package/src/verax/shared/expectation-coverage.js +44 -0
  115. package/src/verax/shared/expectation-prover.js +81 -0
  116. package/src/verax/shared/expectation-tracker.js +201 -0
  117. package/src/verax/shared/expectations-writer.js +60 -0
  118. package/src/verax/shared/first-run.js +44 -0
  119. package/src/verax/shared/progress-reporter.js +171 -0
  120. package/src/verax/shared/retry-policy.js +14 -1
  121. package/src/verax/shared/root-artifacts.js +49 -0
  122. package/src/verax/shared/scan-budget.js +86 -0
  123. package/src/verax/shared/url-normalizer.js +162 -0
  124. package/src/verax/shared/zip-artifacts.js +65 -0
  125. package/src/verax/validate/context-validator.js +244 -0
  126. package/src/verax/validate/context-validator.js.bak +0 -0
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Wave 3 — Error Normalizer
3
+ *
4
+ * Converts technical errors into human-friendly messages with next steps.
5
+ */
6
+
7
+ /**
8
+ * Normalize error into user-friendly message
9
+ * @param {Error} error - Error object
10
+ * @param {Object} context - Context information
11
+ * @param {boolean} debug - Whether to show full stack
12
+ * @returns {Object} { message: string, nextSteps: string[], stack: string }
13
+ */
14
+ export function normalizeError(error, context = {}, debug = false) {
15
+ const errorMessage = error.message || String(error);
16
+ const stack = error.stack || '';
17
+
18
+ // Missing URL
19
+ if (errorMessage.includes('--url is required') || errorMessage.includes('URL is required')) {
20
+ return {
21
+ message: 'URL is required to run a scan.',
22
+ nextSteps: [
23
+ 'Try: verax run --url http://localhost:3000',
24
+ 'Or run: verax (interactive wizard)'
25
+ ],
26
+ stack: debug ? stack : null
27
+ };
28
+ }
29
+
30
+ // Project root not found
31
+ if (errorMessage.includes('does not exist') && context.projectRoot) {
32
+ return {
33
+ message: `Project directory not found: ${context.projectRoot}`,
34
+ nextSteps: [
35
+ 'Check that the path is correct',
36
+ 'Or specify a different path with --projectRoot',
37
+ 'Or run from the project directory: cd /path/to/project && verax run --url <url>'
38
+ ],
39
+ stack: debug ? stack : null
40
+ };
41
+ }
42
+
43
+ // INVALID_CONTEXT
44
+ if (errorMessage.includes('INVALID_CONTEXT') || context.verdict === 'INVALID_CONTEXT') {
45
+ return {
46
+ message: 'The URL you\'re scanning doesn\'t match the project being analyzed.',
47
+ nextSteps: [
48
+ 'Ensure the URL matches your project (e.g., localhost:3000 for local dev server)',
49
+ 'Or use --force to scan anyway (not recommended)',
50
+ 'Or specify the correct --projectRoot that matches the URL'
51
+ ],
52
+ stack: debug ? stack : null
53
+ };
54
+ }
55
+
56
+ // NO_EXPECTATIONS_FOUND
57
+ if (errorMessage.includes('NO_EXPECTATIONS_FOUND') || context.verdict === 'NO_EXPECTATIONS_FOUND') {
58
+ return {
59
+ message: 'No code-derived expectations found in your project.',
60
+ nextSteps: [
61
+ 'VERAX needs static, proven patterns in your code:',
62
+ ' • HTML links: <a href="/about">',
63
+ ' • Static fetch/axios: fetch("/api/users") (no template literals)',
64
+ ' • State mutations: useState, Redux dispatch, Zustand set',
65
+ 'Dynamic routes and URLs are intentionally skipped.',
66
+ 'See README for supported patterns.'
67
+ ],
68
+ stack: debug ? stack : null
69
+ };
70
+ }
71
+
72
+ // Playwright launch failure
73
+ const lowerError = errorMessage.toLowerCase();
74
+ const lowerStack = stack.toLowerCase();
75
+ if (lowerError.includes('executable') ||
76
+ lowerError.includes('browsertype') ||
77
+ lowerError.includes('chromium') ||
78
+ lowerError.includes('playwright') ||
79
+ lowerStack.includes('playwright')) {
80
+ return {
81
+ message: 'Browser automation failed. Playwright browsers are not installed.',
82
+ nextSteps: [
83
+ 'Install Playwright browsers: npx playwright install chromium',
84
+ 'Or with system dependencies: npx playwright install --with-deps chromium',
85
+ 'See: https://playwright.dev/docs/installation'
86
+ ],
87
+ stack: debug ? stack : null
88
+ };
89
+ }
90
+
91
+ // Network/navigation errors
92
+ if (errorMessage.includes('net::ERR') || errorMessage.includes('Navigation timeout') || errorMessage.includes('Protocol error')) {
93
+ return {
94
+ message: `Cannot connect to ${context.url || 'the URL'}.`,
95
+ nextSteps: [
96
+ 'Ensure the URL is correct and the server is running',
97
+ 'Check network connectivity',
98
+ 'For localhost: ensure your dev server is started'
99
+ ],
100
+ stack: debug ? stack : null
101
+ };
102
+ }
103
+
104
+ // Flow file not found
105
+ if (errorMessage.includes('Flow file not found')) {
106
+ return {
107
+ message: `Flow file not found: ${context.flowPath || 'specified path'}`,
108
+ nextSteps: [
109
+ 'Check that the file path is correct',
110
+ 'Ensure the flow file exists',
111
+ 'See README for flow file format'
112
+ ],
113
+ stack: debug ? stack : null
114
+ };
115
+ }
116
+
117
+ // Generic error
118
+ return {
119
+ message: errorMessage,
120
+ nextSteps: [
121
+ 'Check the error message above',
122
+ 'Run with --debug for detailed error information',
123
+ 'See README or run: verax --help'
124
+ ],
125
+ stack: debug ? stack : null
126
+ };
127
+ }
128
+
129
+ /**
130
+ * Print normalized error to console
131
+ * @param {Error} error - Error object
132
+ * @param {Object} context - Context
133
+ * @param {boolean} debug - Debug mode
134
+ */
135
+ export function printNormalizedError(error, context = {}, debug = false) {
136
+ const normalized = normalizeError(error, context, debug);
137
+
138
+ console.error(`\nError: ${normalized.message}\n`);
139
+
140
+ if (normalized.nextSteps && normalized.nextSteps.length > 0) {
141
+ console.error('Next steps:');
142
+ normalized.nextSteps.forEach(step => {
143
+ console.error(` ${step}`);
144
+ });
145
+ console.error('');
146
+ }
147
+
148
+ if (normalized.stack && debug) {
149
+ console.error('Stack trace:');
150
+ console.error(normalized.stack);
151
+ console.error('');
152
+ }
153
+ }
154
+
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Wave 4 — Explain Output
3
+ *
4
+ * Human-readable explanation of expectations and their usage.
5
+ */
6
+
7
+ /**
8
+ * Format expectation for --explain output
9
+ * @param {Object} expectation - Expectation object from expectations.json
10
+ * @returns {string} Formatted string
11
+ */
12
+ function formatExpectation(expectation) {
13
+ const lines = [];
14
+
15
+ lines.push(` ID: ${expectation.id}`);
16
+ lines.push(` Type: ${expectation.type}`);
17
+ lines.push(` Proof: ${expectation.proof}`);
18
+ lines.push(` Reason: ${expectation.reason}`);
19
+
20
+ if (expectation.source) {
21
+ const sourceStr = expectation.source.file
22
+ ? `${expectation.source.file}${expectation.source.line ? `:${expectation.source.line}` : ''}`
23
+ : 'unknown source';
24
+ lines.push(` Source: ${sourceStr}`);
25
+ }
26
+
27
+ lines.push(` Used: ${expectation.used ? 'YES' : 'NO'}`);
28
+
29
+ if (expectation.usedReason) {
30
+ lines.push(` Used Reason: ${expectation.usedReason}`);
31
+ } else if (!expectation.used) {
32
+ lines.push(` Not Used: ${expectation.usedReason || 'no matching interaction'}`);
33
+ }
34
+
35
+ return lines.join('\n');
36
+ }
37
+
38
+ /**
39
+ * Print explain output
40
+ * @param {Array} expectations - Array of expectation objects
41
+ * @param {Object} summary - Expectations summary
42
+ */
43
+ export function printExplainOutput(expectations, summary) {
44
+ console.error('\n' + '═'.repeat(60));
45
+ console.error('VERAX Expectations Explanation');
46
+ console.error('═'.repeat(60));
47
+ console.error('');
48
+
49
+ console.error(`Summary:`);
50
+ console.error(` Total expectations: ${summary.total}`);
51
+ console.error(` By type: navigation=${summary.byType.navigation}, network_action=${summary.byType.network_action}, state_action=${summary.byType.state_action}`);
52
+ console.error(` Used: ${summary.total - summary.skipped}`);
53
+ console.error(` Unused: ${summary.skipped}`);
54
+ console.error('');
55
+
56
+ if (expectations.length === 0) {
57
+ console.error('No expectations found in your project.');
58
+ console.error('VERAX needs static code patterns to create expectations.');
59
+ console.error('See README for supported patterns.');
60
+ console.error('');
61
+ return;
62
+ }
63
+
64
+ // Group by type
65
+ const byType = {
66
+ navigation: expectations.filter(e => e.type === 'navigation'),
67
+ network_action: expectations.filter(e => e.type === 'network_action'),
68
+ state_action: expectations.filter(e => e.type === 'state_action')
69
+ };
70
+
71
+ // Print by type
72
+ for (const [type, exps] of Object.entries(byType)) {
73
+ if (exps.length === 0) continue;
74
+
75
+ console.error(`${type.toUpperCase().replace('_', ' ')} (${exps.length}):`);
76
+ console.error('─'.repeat(60));
77
+
78
+ for (const exp of exps) {
79
+ console.error(formatExpectation(exp));
80
+ console.error('');
81
+ }
82
+ }
83
+
84
+ // Summary of unused expectations
85
+ const unused = expectations.filter(e => !e.used);
86
+ if (unused.length > 0) {
87
+ console.error('Unused Expectations Summary:');
88
+ console.error('─'.repeat(60));
89
+
90
+ const unusedByReason = {};
91
+ for (const exp of unused) {
92
+ const reason = exp.usedReason || 'no matching interaction';
93
+ unusedByReason[reason] = (unusedByReason[reason] || 0) + 1;
94
+ }
95
+
96
+ for (const [reason, count] of Object.entries(unusedByReason)) {
97
+ console.error(` ${reason}: ${count}`);
98
+ }
99
+ console.error('');
100
+ }
101
+
102
+ console.error('═'.repeat(60));
103
+ console.error('');
104
+ }
105
+
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Wave 6 — Finding Explanation
3
+ *
4
+ * Formats findings in a clear, human-readable way showing the chain:
5
+ * Expectation → Observation → Mismatch → Why this is a silent failure
6
+ */
7
+
8
+ /**
9
+ * Format a single finding for console output
10
+ * @param {Object} finding - Finding object
11
+ * @param {Object} expectation - Related expectation (if available)
12
+ * @returns {string} Formatted finding text
13
+ */
14
+ export function formatFinding(finding, expectation = null) {
15
+ const lines = [];
16
+
17
+ // Finding type and confidence
18
+ const confidenceLevel = finding.confidence?.level || 'UNKNOWN';
19
+ const confidenceScore = finding.confidence?.score || 0;
20
+ lines.push(` [${confidenceLevel} (${confidenceScore}%)] ${finding.type || 'unknown'}`);
21
+
22
+ // Expectation (what was promised)
23
+ if (expectation) {
24
+ let expectationDesc = '';
25
+ if (expectation.type === 'navigation') {
26
+ const target = expectation.raw?.targetPath || expectation.targetPath || 'route';
27
+ expectationDesc = `Expected navigation to ${target}`;
28
+ } else if (expectation.type === 'network_action') {
29
+ const method = expectation.raw?.method || 'request';
30
+ const url = expectation.raw?.urlPath || expectation.urlPath || 'endpoint';
31
+ expectationDesc = `Expected ${method} request to ${url}`;
32
+ } else if (expectation.type === 'state_action') {
33
+ expectationDesc = `Expected state mutation`;
34
+ } else {
35
+ expectationDesc = `Expected ${expectation.type}`;
36
+ }
37
+ lines.push(` └─ Expectation: ${expectationDesc}`);
38
+ } else if (finding.expectationId) {
39
+ lines.push(` └─ Expectation: Referenced expectation ${finding.expectationId}`);
40
+ }
41
+
42
+ // Observation (what actually happened)
43
+ const interaction = finding.interaction || {};
44
+ if (interaction.type) {
45
+ let interactionDesc = `${interaction.type}`;
46
+ if (interaction.selector) {
47
+ interactionDesc += ` on "${interaction.label || interaction.selector}"`;
48
+ }
49
+ lines.push(` └─ Observation: User interacted (${interactionDesc})`);
50
+ }
51
+
52
+ // Mismatch (what went wrong)
53
+ const evidence = finding.evidence || {};
54
+ if (finding.type === 'silent_failure' || finding.type === 'navigation_silent_failure') {
55
+ if (!evidence.hasUrlChange && !evidence.hasVisibleChange) {
56
+ lines.push(` └─ Mismatch: No navigation occurred, no visible change`);
57
+ } else if (evidence.hasUrlChange && !evidence.hasVisibleChange) {
58
+ lines.push(` └─ Mismatch: URL changed but no visible feedback`);
59
+ } else {
60
+ lines.push(` └─ Mismatch: Expected navigation did not occur`);
61
+ }
62
+ } else if (finding.type === 'network_silent_failure') {
63
+ if (evidence.slowRequests && evidence.slowRequests > 0) {
64
+ lines.push(` └─ Mismatch: Request was slow (${evidence.slowRequests} slow request(s))`);
65
+ } else {
66
+ lines.push(` └─ Mismatch: Request failed or returned error with no user feedback`);
67
+ }
68
+ } else if (finding.type === 'missing_network_action') {
69
+ lines.push(` └─ Mismatch: Expected network request never fired`);
70
+ } else if (finding.type === 'missing_state_action') {
71
+ lines.push(` └─ Mismatch: Expected state change did not occur`);
72
+ }
73
+
74
+ // Why this is a silent failure
75
+ if (finding.reason) {
76
+ lines.push(` └─ Silent Failure: ${finding.reason}`);
77
+ } else {
78
+ lines.push(` └─ Silent Failure: User receives no feedback when expected behavior fails`);
79
+ }
80
+
81
+ // Source location if available
82
+ if (expectation?.source?.file) {
83
+ const sourceLine = expectation.source.line ? `:${expectation.source.line}` : '';
84
+ lines.push(` └─ Source: ${expectation.source.file}${sourceLine}`);
85
+ }
86
+
87
+ return lines.join('\n');
88
+ }
89
+
90
+ /**
91
+ * Print top findings with explanations
92
+ * @param {Array} findings - Array of finding objects
93
+ * @param {Array} expectations - Array of expectation objects (for lookup)
94
+ * @param {number} limit - Maximum number of findings to print
95
+ */
96
+ export function printFindings(findings, expectations = [], limit = 5) {
97
+ if (!findings || findings.length === 0) {
98
+ return;
99
+ }
100
+
101
+ console.error('\n' + '─'.repeat(60));
102
+ console.error(`Top Findings (${Math.min(findings.length, limit)} of ${findings.length})`);
103
+ console.error('─'.repeat(60));
104
+
105
+ const topFindings = findings.slice(0, limit);
106
+
107
+ // Create expectation lookup map
108
+ const expectationMap = new Map();
109
+ for (const exp of expectations) {
110
+ if (exp.id) {
111
+ expectationMap.set(exp.id, exp);
112
+ }
113
+ }
114
+
115
+ topFindings.forEach((finding, index) => {
116
+ const expectation = finding.expectationId
117
+ ? expectationMap.get(finding.expectationId)
118
+ : null;
119
+
120
+ console.error(`\n${index + 1}.`);
121
+ console.error(formatFinding(finding, expectation));
122
+ });
123
+
124
+ if (findings.length > limit) {
125
+ console.error(`\n... and ${findings.length - limit} more (see findings.json)`);
126
+ }
127
+
128
+ console.error('─'.repeat(60) + '\n');
129
+ }
130
+
@@ -0,0 +1,237 @@
1
+ /**
2
+ * Wave 7 — VERAX Init
3
+ *
4
+ * Initializes VERAX configuration and templates.
5
+ */
6
+
7
+ import { existsSync, writeFileSync, mkdirSync } from 'fs';
8
+ import { resolve, dirname } from 'path';
9
+ import { getDefaultConfig } from '../shared/config-loader.js';
10
+
11
+ /**
12
+ * Initialize VERAX configuration
13
+ * @param {Object} options - { projectRoot, yes, ciTemplate, flowTemplate }
14
+ * @returns {Promise<Object>} { created: string[], skipped: string[] }
15
+ */
16
+ export async function runInit(options = {}) {
17
+ const {
18
+ projectRoot = process.cwd(),
19
+ yes = false,
20
+ ciTemplate = null,
21
+ flowTemplate = null
22
+ } = options;
23
+
24
+ const created = [];
25
+ const skipped = [];
26
+
27
+ // Create .verax directory if needed
28
+ const veraxDir = resolve(projectRoot, '.verax');
29
+ mkdirSync(veraxDir, { recursive: true });
30
+
31
+ // Create config.json
32
+ const configPath = resolve(veraxDir, 'config.json');
33
+ if (existsSync(configPath) && !yes) {
34
+ skipped.push('config.json');
35
+ } else {
36
+ const defaultConfig = getDefaultConfig();
37
+ writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2) + '\n');
38
+ created.push('config.json');
39
+ }
40
+
41
+ // Create CI template if requested
42
+ if (ciTemplate === 'github') {
43
+ const workflowsDir = resolve(projectRoot, '.github', 'workflows');
44
+ mkdirSync(workflowsDir, { recursive: true });
45
+
46
+ const workflowFile = resolve(workflowsDir, 'verax-ci.yml');
47
+ if (existsSync(workflowFile) && !yes) {
48
+ skipped.push('.github/workflows/verax-ci.yml');
49
+ } else {
50
+ const workflowContent = `name: VERAX CI
51
+
52
+ on:
53
+ workflow_dispatch:
54
+ pull_request:
55
+
56
+ jobs:
57
+ verax-scan:
58
+ runs-on: ubuntu-latest
59
+ steps:
60
+ - name: Checkout
61
+ uses: actions/checkout@v4
62
+
63
+ - name: Setup Node.js
64
+ uses: actions/setup-node@v4
65
+ with:
66
+ node-version: '20'
67
+ cache: 'npm'
68
+
69
+ - name: Install dependencies
70
+ run: npm ci
71
+
72
+ - name: Install Playwright browsers
73
+ run: npx playwright install --with-deps chromium
74
+
75
+ - name: Start fixture server
76
+ id: fixture-server
77
+ run: |
78
+ node test/helpers/fixture-server.js &
79
+ SERVER_PID=$!
80
+ echo "SERVER_PID=$SERVER_PID" >> $GITHUB_ENV
81
+ sleep 3
82
+ echo "url=http://127.0.0.1:8888" >> $GITHUB_OUTPUT
83
+ working-directory: \${{ github.workspace }}
84
+
85
+ - name: Run VERAX CI scan
86
+ id: verax
87
+ run: |
88
+ npx @veraxhq/verax ci --url \${{ steps.fixture-server.outputs.url }} --projectRoot .
89
+ continue-on-error: true
90
+
91
+ - name: Stop fixture server
92
+ if: always()
93
+ run: |
94
+ if [ ! -z "$SERVER_PID" ]; then
95
+ kill $SERVER_PID 2>/dev/null || true
96
+ fi
97
+
98
+ - name: Upload VERAX artifacts
99
+ if: always()
100
+ uses: actions/upload-artifact@v4
101
+ with:
102
+ name: verax-artifacts
103
+ path: |
104
+ .verax/runs/**/*
105
+ .verax/verax-run-*.zip
106
+ retention-days: 7
107
+ if-no-files-found: ignore
108
+
109
+ - name: Check VERAX exit code and fail gate
110
+ if: always()
111
+ run: |
112
+ EXIT_CODE=\${{ steps.verax.exitcode }}
113
+ if [ "$EXIT_CODE" == "" ]; then
114
+ EXIT_CODE=\${{ steps.verax.outcome == 'success' && 0 || 1 }}
115
+ fi
116
+
117
+ if [ "$EXIT_CODE" == "0" ]; then
118
+ echo "✅ VERAX: VERIFIED - Scan passed"
119
+ elif [ "$EXIT_CODE" == "1" ]; then
120
+ echo "⚠️ VERAX: NO_EXPECTATIONS_FOUND or MEDIUM/LOW findings"
121
+ echo "Gate passes (non-blocking)"
122
+ elif [ "$EXIT_CODE" == "2" ]; then
123
+ echo "❌ VERAX: HIGH severity findings detected"
124
+ echo "Gate fails (blocking)"
125
+ exit 2
126
+ elif [ "$EXIT_CODE" == "4" ]; then
127
+ echo "❌ VERAX: INVALID_CONTEXT - URL does not match project"
128
+ echo "Gate fails (blocking)"
129
+ exit 4
130
+ elif [ "$EXIT_CODE" == "3" ]; then
131
+ echo "❌ VERAX: FATAL error"
132
+ echo "Gate fails (blocking)"
133
+ exit 3
134
+ else
135
+ echo "❌ VERAX: Unexpected exit code $EXIT_CODE"
136
+ exit $EXIT_CODE
137
+ fi
138
+ `;
139
+ writeFileSync(workflowFile, workflowContent);
140
+ created.push('.github/workflows/verax-ci.yml');
141
+ }
142
+ }
143
+
144
+ // Create flow template if requested
145
+ if (flowTemplate === 'login') {
146
+ const flowsDir = resolve(projectRoot, 'flows');
147
+ mkdirSync(flowsDir, { recursive: true });
148
+
149
+ const flowFile = resolve(flowsDir, 'login.json');
150
+ if (existsSync(flowFile) && !yes) {
151
+ skipped.push('flows/login.json');
152
+ } else {
153
+ const flowContent = {
154
+ name: 'login',
155
+ description: 'User login flow',
156
+ steps: [
157
+ {
158
+ type: 'goto',
159
+ url: '${VERAX_BASE_URL}/login'
160
+ },
161
+ {
162
+ type: 'fill',
163
+ selector: 'input[name="email"]',
164
+ value: '${VERAX_TEST_EMAIL}'
165
+ },
166
+ {
167
+ type: 'fill',
168
+ selector: 'input[name="password"]',
169
+ value: '${VERAX_TEST_PASSWORD}',
170
+ isSecret: true
171
+ },
172
+ {
173
+ type: 'click',
174
+ selector: 'button[type="submit"]'
175
+ },
176
+ {
177
+ type: 'wait',
178
+ selector: '[data-testid="dashboard"]',
179
+ timeout: 5000
180
+ }
181
+ ]
182
+ };
183
+ writeFileSync(flowFile, JSON.stringify(flowContent, null, 2) + '\n');
184
+ created.push('flows/login.json');
185
+ }
186
+ }
187
+
188
+ return { created, skipped };
189
+ }
190
+
191
+ /**
192
+ * Print init results
193
+ * @param {Object} results - Init results
194
+ */
195
+ export function printInitResults(results) {
196
+ console.error('\n' + '═'.repeat(60));
197
+ console.error('VERAX Init');
198
+ console.error('═'.repeat(60));
199
+
200
+ if (results.created.length > 0) {
201
+ console.error('\n✅ Created:');
202
+ results.created.forEach(file => {
203
+ console.error(` • ${file}`);
204
+ });
205
+ }
206
+
207
+ if (results.skipped.length > 0) {
208
+ console.error('\n⏭️ Skipped (already exist):');
209
+ results.skipped.forEach(file => {
210
+ console.error(` • ${file}`);
211
+ });
212
+ }
213
+
214
+ if (results.created.includes('config.json')) {
215
+ console.error('\n📝 Next Steps:');
216
+ console.error(' 1. Review .verax/config.json and adjust settings');
217
+ console.error(' 2. Run: verax doctor (to verify setup)');
218
+ console.error(' 3. Run: verax run --url <your-url>');
219
+ }
220
+
221
+ if (results.created.includes('.github/workflows/verax-ci.yml')) {
222
+ console.error('\n🔧 CI Setup:');
223
+ console.error(' • Review .github/workflows/verax-ci.yml');
224
+ console.error(' • Update URL placeholder with your deployment URL');
225
+ console.error(' • Commit and push to enable VERAX in CI');
226
+ }
227
+
228
+ if (results.created.includes('flows/login.json')) {
229
+ console.error('\n🔐 Flow Template:');
230
+ console.error(' • Review flows/login.json');
231
+ console.error(' • Set VERAX_TEST_EMAIL and VERAX_TEST_PASSWORD environment variables');
232
+ console.error(' • Note: Flow commands are not available in this version');
233
+ }
234
+
235
+ console.error('═'.repeat(60) + '\n');
236
+ }
237
+