@veraxhq/verax 0.2.0 → 0.3.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.
- package/README.md +14 -18
- package/bin/verax.js +7 -0
- package/package.json +15 -5
- package/src/cli/commands/baseline.js +104 -0
- package/src/cli/commands/default.js +323 -111
- package/src/cli/commands/doctor.js +36 -4
- package/src/cli/commands/ga.js +243 -0
- package/src/cli/commands/gates.js +95 -0
- package/src/cli/commands/inspect.js +131 -2
- package/src/cli/commands/release-check.js +213 -0
- package/src/cli/commands/run.js +498 -103
- package/src/cli/commands/security-check.js +211 -0
- package/src/cli/commands/truth.js +114 -0
- package/src/cli/entry.js +305 -68
- package/src/cli/util/angular-component-extractor.js +179 -0
- package/src/cli/util/angular-navigation-detector.js +141 -0
- package/src/cli/util/angular-network-detector.js +161 -0
- package/src/cli/util/angular-state-detector.js +162 -0
- package/src/cli/util/ast-interactive-detector.js +546 -0
- package/src/cli/util/ast-network-detector.js +603 -0
- package/src/cli/util/ast-usestate-detector.js +602 -0
- package/src/cli/util/bootstrap-guard.js +86 -0
- package/src/cli/util/detection-engine.js +4 -3
- package/src/cli/util/determinism-runner.js +123 -0
- package/src/cli/util/determinism-writer.js +129 -0
- package/src/cli/util/env-url.js +4 -0
- package/src/cli/util/events.js +76 -0
- package/src/cli/util/expectation-extractor.js +380 -74
- package/src/cli/util/findings-writer.js +126 -15
- package/src/cli/util/learn-writer.js +3 -1
- package/src/cli/util/observation-engine.js +69 -23
- package/src/cli/util/observe-writer.js +3 -1
- package/src/cli/util/paths.js +6 -14
- package/src/cli/util/project-discovery.js +23 -0
- package/src/cli/util/project-writer.js +3 -1
- package/src/cli/util/redact.js +2 -2
- package/src/cli/util/run-resolver.js +64 -0
- package/src/cli/util/runtime-budget.js +147 -0
- package/src/cli/util/source-requirement.js +55 -0
- package/src/cli/util/summary-writer.js +13 -1
- package/src/cli/util/svelte-navigation-detector.js +163 -0
- package/src/cli/util/svelte-network-detector.js +80 -0
- package/src/cli/util/svelte-sfc-extractor.js +147 -0
- package/src/cli/util/svelte-state-detector.js +243 -0
- package/src/cli/util/vue-navigation-detector.js +177 -0
- package/src/cli/util/vue-sfc-extractor.js +162 -0
- package/src/cli/util/vue-state-detector.js +215 -0
- package/src/types/global.d.ts +28 -0
- package/src/types/ts-ast.d.ts +24 -0
- package/src/verax/cli/doctor.js +2 -2
- package/src/verax/cli/finding-explainer.js +56 -3
- package/src/verax/cli/init.js +1 -1
- package/src/verax/cli/url-safety.js +12 -2
- package/src/verax/cli/wizard.js +13 -2
- package/src/verax/core/artifacts/registry.js +154 -0
- package/src/verax/core/artifacts/verifier.js +980 -0
- package/src/verax/core/baseline/baseline.enforcer.js +137 -0
- package/src/verax/core/baseline/baseline.snapshot.js +231 -0
- package/src/verax/core/budget-engine.js +1 -1
- package/src/verax/core/capabilities/gates.js +499 -0
- package/src/verax/core/capabilities/registry.js +475 -0
- package/src/verax/core/confidence/confidence-compute.js +137 -0
- package/src/verax/core/confidence/confidence-invariants.js +234 -0
- package/src/verax/core/confidence/confidence-report-writer.js +112 -0
- package/src/verax/core/confidence/confidence-weights.js +44 -0
- package/src/verax/core/confidence/confidence.defaults.js +65 -0
- package/src/verax/core/confidence/confidence.loader.js +79 -0
- package/src/verax/core/confidence/confidence.schema.js +94 -0
- package/src/verax/core/confidence-engine-refactor.js +484 -0
- package/src/verax/core/confidence-engine.js +486 -0
- package/src/verax/core/confidence-engine.js.backup +471 -0
- package/src/verax/core/contracts/index.js +29 -0
- package/src/verax/core/contracts/types.js +185 -0
- package/src/verax/core/contracts/validators.js +381 -0
- package/src/verax/core/decision-snapshot.js +31 -4
- package/src/verax/core/decisions/decision.trace.js +276 -0
- package/src/verax/core/determinism/contract-writer.js +89 -0
- package/src/verax/core/determinism/contract.js +139 -0
- package/src/verax/core/determinism/diff.js +364 -0
- package/src/verax/core/determinism/engine.js +221 -0
- package/src/verax/core/determinism/finding-identity.js +148 -0
- package/src/verax/core/determinism/normalize.js +438 -0
- package/src/verax/core/determinism/report-writer.js +92 -0
- package/src/verax/core/determinism/run-fingerprint.js +118 -0
- package/src/verax/core/determinism-model.js +35 -6
- package/src/verax/core/dynamic-route-intelligence.js +528 -0
- package/src/verax/core/evidence/evidence-capture-service.js +307 -0
- package/src/verax/core/evidence/evidence-intent-ledger.js +165 -0
- package/src/verax/core/evidence-builder.js +487 -0
- package/src/verax/core/execution-mode-context.js +77 -0
- package/src/verax/core/execution-mode-detector.js +190 -0
- package/src/verax/core/failures/exit-codes.js +86 -0
- package/src/verax/core/failures/failure-summary.js +76 -0
- package/src/verax/core/failures/failure.factory.js +225 -0
- package/src/verax/core/failures/failure.ledger.js +132 -0
- package/src/verax/core/failures/failure.types.js +196 -0
- package/src/verax/core/failures/index.js +10 -0
- package/src/verax/core/ga/ga-report-writer.js +43 -0
- package/src/verax/core/ga/ga.artifact.js +49 -0
- package/src/verax/core/ga/ga.contract.js +434 -0
- package/src/verax/core/ga/ga.enforcer.js +86 -0
- package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
- package/src/verax/core/guardrails/policy.defaults.js +210 -0
- package/src/verax/core/guardrails/policy.loader.js +83 -0
- package/src/verax/core/guardrails/policy.schema.js +110 -0
- package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
- package/src/verax/core/guardrails-engine.js +505 -0
- package/src/verax/core/incremental-store.js +15 -7
- package/src/verax/core/observe/run-timeline.js +316 -0
- package/src/verax/core/perf/perf.contract.js +186 -0
- package/src/verax/core/perf/perf.display.js +65 -0
- package/src/verax/core/perf/perf.enforcer.js +91 -0
- package/src/verax/core/perf/perf.monitor.js +209 -0
- package/src/verax/core/perf/perf.report.js +198 -0
- package/src/verax/core/pipeline-tracker.js +238 -0
- package/src/verax/core/product-definition.js +127 -0
- package/src/verax/core/release/provenance.builder.js +271 -0
- package/src/verax/core/release/release-report-writer.js +40 -0
- package/src/verax/core/release/release.enforcer.js +159 -0
- package/src/verax/core/release/reproducibility.check.js +221 -0
- package/src/verax/core/release/sbom.builder.js +283 -0
- package/src/verax/core/replay-validator.js +4 -4
- package/src/verax/core/replay.js +1 -1
- package/src/verax/core/report/cross-index.js +192 -0
- package/src/verax/core/report/human-summary.js +222 -0
- package/src/verax/core/route-intelligence.js +419 -0
- package/src/verax/core/security/secrets.scan.js +326 -0
- package/src/verax/core/security/security-report.js +50 -0
- package/src/verax/core/security/security.enforcer.js +124 -0
- package/src/verax/core/security/supplychain.defaults.json +38 -0
- package/src/verax/core/security/supplychain.policy.js +326 -0
- package/src/verax/core/security/vuln.scan.js +265 -0
- package/src/verax/core/silence-impact.js +1 -1
- package/src/verax/core/silence-model.js +9 -7
- package/src/verax/core/truth/truth.certificate.js +250 -0
- package/src/verax/core/ui-feedback-intelligence.js +515 -0
- package/src/verax/detect/comparison.js +8 -3
- package/src/verax/detect/confidence-engine.js +645 -57
- package/src/verax/detect/confidence-helper.js +33 -0
- package/src/verax/detect/detection-engine.js +19 -2
- package/src/verax/detect/dynamic-route-findings.js +335 -0
- package/src/verax/detect/evidence-index.js +15 -65
- package/src/verax/detect/expectation-chain-detector.js +417 -0
- package/src/verax/detect/expectation-model.js +56 -3
- package/src/verax/detect/explanation-helpers.js +1 -1
- package/src/verax/detect/finding-detector.js +2 -2
- package/src/verax/detect/findings-writer.js +149 -20
- package/src/verax/detect/flow-detector.js +4 -4
- package/src/verax/detect/index.js +265 -15
- package/src/verax/detect/interactive-findings.js +3 -4
- package/src/verax/detect/journey-stall-detector.js +558 -0
- package/src/verax/detect/route-findings.js +218 -0
- package/src/verax/detect/signal-mapper.js +2 -2
- package/src/verax/detect/skip-classifier.js +4 -4
- package/src/verax/detect/ui-feedback-findings.js +207 -0
- package/src/verax/detect/verdict-engine.js +61 -9
- package/src/verax/detect/view-switch-correlator.js +242 -0
- package/src/verax/flow/flow-engine.js +3 -2
- package/src/verax/flow/flow-spec.js +1 -2
- package/src/verax/index.js +413 -33
- package/src/verax/intel/effect-detector.js +1 -1
- package/src/verax/intel/index.js +2 -2
- package/src/verax/intel/route-extractor.js +3 -3
- package/src/verax/intel/vue-navigation-extractor.js +81 -18
- package/src/verax/intel/vue-router-extractor.js +4 -2
- package/src/verax/learn/action-contract-extractor.js +684 -66
- package/src/verax/learn/ast-contract-extractor.js +53 -1
- package/src/verax/learn/index.js +36 -2
- package/src/verax/learn/manifest-writer.js +28 -14
- package/src/verax/learn/route-extractor.js +1 -1
- package/src/verax/learn/route-validator.js +12 -8
- package/src/verax/learn/state-extractor.js +1 -1
- package/src/verax/learn/static-extractor-navigation.js +1 -1
- package/src/verax/learn/static-extractor-validation.js +2 -2
- package/src/verax/learn/static-extractor.js +8 -7
- package/src/verax/learn/ts-contract-resolver.js +14 -12
- package/src/verax/observe/browser.js +22 -3
- package/src/verax/observe/console-sensor.js +2 -2
- package/src/verax/observe/expectation-executor.js +2 -1
- package/src/verax/observe/focus-sensor.js +1 -1
- package/src/verax/observe/human-driver.js +29 -10
- package/src/verax/observe/index.js +92 -844
- package/src/verax/observe/interaction-discovery.js +27 -15
- package/src/verax/observe/interaction-runner.js +31 -14
- package/src/verax/observe/loading-sensor.js +6 -0
- package/src/verax/observe/navigation-sensor.js +1 -1
- package/src/verax/observe/observe-context.js +205 -0
- package/src/verax/observe/observe-helpers.js +191 -0
- package/src/verax/observe/observe-runner.js +226 -0
- package/src/verax/observe/observers/budget-observer.js +185 -0
- package/src/verax/observe/observers/console-observer.js +102 -0
- package/src/verax/observe/observers/coverage-observer.js +107 -0
- package/src/verax/observe/observers/interaction-observer.js +471 -0
- package/src/verax/observe/observers/navigation-observer.js +132 -0
- package/src/verax/observe/observers/network-observer.js +87 -0
- package/src/verax/observe/observers/safety-observer.js +82 -0
- package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
- package/src/verax/observe/settle.js +1 -0
- package/src/verax/observe/state-sensor.js +8 -4
- package/src/verax/observe/state-ui-sensor.js +7 -1
- package/src/verax/observe/traces-writer.js +27 -16
- package/src/verax/observe/ui-feedback-detector.js +742 -0
- package/src/verax/observe/ui-signal-sensor.js +155 -2
- package/src/verax/scan-summary-writer.js +46 -9
- package/src/verax/shared/artifact-manager.js +9 -6
- package/src/verax/shared/budget-profiles.js +2 -2
- package/src/verax/shared/caching.js +1 -1
- package/src/verax/shared/config-loader.js +1 -2
- package/src/verax/shared/css-spinner-rules.js +204 -0
- package/src/verax/shared/dynamic-route-utils.js +12 -6
- package/src/verax/shared/retry-policy.js +1 -6
- package/src/verax/shared/root-artifacts.js +1 -1
- package/src/verax/shared/view-switch-rules.js +208 -0
- package/src/verax/shared/zip-artifacts.js +1 -0
- package/src/verax/validate/context-validator.js +1 -1
- package/src/verax/observe/index.js.backup +0 -1
- package/src/verax/validate/context-validator.js.bak +0 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime Budget Model
|
|
3
|
+
* Computes timeouts based on project size, execution mode, and framework
|
|
4
|
+
* Ensures deterministic, bounded execution times
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {Object} RuntimeBudgetOptions
|
|
9
|
+
* @property {number} [expectationsCount=0] - Number of expectations to process
|
|
10
|
+
* @property {string} [mode='default'] - Execution mode: 'default', 'run', 'ci'
|
|
11
|
+
* @property {string} [framework='unknown'] - Detected framework (optional)
|
|
12
|
+
* @property {number|null} [fileCount=null] - Number of files scanned (optional, fallback to expectationsCount)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Compute runtime budgets for a VERAX run
|
|
17
|
+
* @param {RuntimeBudgetOptions} [options={}] - Budget computation options
|
|
18
|
+
* @returns {Object} Budget object with phase timeouts
|
|
19
|
+
*/
|
|
20
|
+
export function computeRuntimeBudget(options = {}) {
|
|
21
|
+
const {
|
|
22
|
+
expectationsCount = 0,
|
|
23
|
+
mode = 'default',
|
|
24
|
+
framework = 'unknown',
|
|
25
|
+
fileCount = null,
|
|
26
|
+
} = options;
|
|
27
|
+
|
|
28
|
+
// TEST MODE OVERRIDE: Fixed deterministic budgets for integration tests
|
|
29
|
+
if (process.env.VERAX_TEST_MODE === '1') {
|
|
30
|
+
return {
|
|
31
|
+
totalMaxMs: 30000, // Hard cap per run
|
|
32
|
+
learnMaxMs: 5000, // Keep learn bounded
|
|
33
|
+
observeMaxMs: 20000, // Deterministic observe budget
|
|
34
|
+
detectMaxMs: 5000, // Bounded detect
|
|
35
|
+
perExpectationMaxMs: 5000, // Deterministic per-expectation guard
|
|
36
|
+
mode: 'test',
|
|
37
|
+
framework,
|
|
38
|
+
expectationsCount,
|
|
39
|
+
projectSize: fileCount !== null ? fileCount : expectationsCount,
|
|
40
|
+
frameworkMultiplier: 1.0,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Use file count if available, otherwise use expectations count as proxy
|
|
45
|
+
const projectSize = fileCount !== null ? fileCount : expectationsCount;
|
|
46
|
+
|
|
47
|
+
// Base timeouts (milliseconds)
|
|
48
|
+
// Small project: < 10 expectations/files
|
|
49
|
+
// Medium project: 10-50 expectations/files
|
|
50
|
+
// Large project: > 50 expectations/files
|
|
51
|
+
|
|
52
|
+
// Learn phase: file scanning and AST parsing
|
|
53
|
+
const learnBaseMs = mode === 'ci' ? 30000 : 60000; // CI: 30s, default: 60s
|
|
54
|
+
const learnPerFileMs = 50; // 50ms per file
|
|
55
|
+
const learnMaxMs = mode === 'ci' ? 120000 : 300000; // CI: 2min, default: 5min
|
|
56
|
+
|
|
57
|
+
// Observe phase: browser automation
|
|
58
|
+
const observeBaseMs = mode === 'ci' ? 60000 : 120000; // CI: 1min, default: 2min
|
|
59
|
+
const observePerExpectationMs = mode === 'ci' ? 2000 : 5000; // CI: 2s, default: 5s per expectation
|
|
60
|
+
const observeMaxMs = mode === 'ci' ? 600000 : 1800000; // CI: 10min, default: 30min
|
|
61
|
+
|
|
62
|
+
// Detect phase: analysis and comparison
|
|
63
|
+
const detectBaseMs = mode === 'ci' ? 15000 : 30000; // CI: 15s, default: 30s
|
|
64
|
+
const detectPerExpectationMs = 100; // 100ms per expectation
|
|
65
|
+
const detectMaxMs = mode === 'ci' ? 120000 : 300000; // CI: 2min, default: 5min
|
|
66
|
+
|
|
67
|
+
// Per-expectation timeout during observe phase
|
|
68
|
+
const perExpectationBaseMs = mode === 'ci' ? 10000 : 30000; // CI: 10s, default: 30s
|
|
69
|
+
const perExpectationMaxMs = 120000; // 2min max per expectation
|
|
70
|
+
|
|
71
|
+
// Framework weighting (some frameworks may need more time)
|
|
72
|
+
let frameworkMultiplier = 1.0;
|
|
73
|
+
if (framework === 'nextjs' || framework === 'remix') {
|
|
74
|
+
frameworkMultiplier = 1.2; // SSR frameworks may need slightly more time
|
|
75
|
+
} else if (framework === 'react' || framework === 'vue') {
|
|
76
|
+
frameworkMultiplier = 1.1; // SPA frameworks
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Compute phase budgets
|
|
80
|
+
const computedLearnMaxMs = Math.min(
|
|
81
|
+
learnBaseMs + (projectSize * learnPerFileMs * frameworkMultiplier),
|
|
82
|
+
learnMaxMs
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const computedObserveMaxMs = Math.min(
|
|
86
|
+
observeBaseMs + (expectationsCount * observePerExpectationMs * frameworkMultiplier),
|
|
87
|
+
observeMaxMs
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const computedDetectMaxMs = Math.min(
|
|
91
|
+
detectBaseMs + (expectationsCount * detectPerExpectationMs * frameworkMultiplier),
|
|
92
|
+
detectMaxMs
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const computedPerExpectationMaxMs = Math.min(
|
|
96
|
+
perExpectationBaseMs * frameworkMultiplier,
|
|
97
|
+
perExpectationMaxMs
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
// Global watchdog timeout (must be >= sum of all phases + buffer)
|
|
101
|
+
// Add 30s buffer for finalization
|
|
102
|
+
const totalMaxMs = Math.max(
|
|
103
|
+
computedLearnMaxMs + computedObserveMaxMs + computedDetectMaxMs + 30000,
|
|
104
|
+
mode === 'ci' ? 900000 : 2400000 // CI: 15min minimum, default: 40min minimum
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// Cap global timeout
|
|
108
|
+
const totalMaxMsCap = mode === 'ci' ? 1800000 : 3600000; // CI: 30min, default: 60min
|
|
109
|
+
const finalTotalMaxMs = Math.min(totalMaxMs, totalMaxMsCap);
|
|
110
|
+
|
|
111
|
+
// Ensure minimums are met
|
|
112
|
+
const finalLearnMaxMs = Math.max(computedLearnMaxMs, 10000); // At least 10s
|
|
113
|
+
const finalObserveMaxMs = Math.max(computedObserveMaxMs, 30000); // At least 30s
|
|
114
|
+
const finalDetectMaxMs = Math.max(computedDetectMaxMs, 5000); // At least 5s
|
|
115
|
+
const finalPerExpectationMaxMs = Math.max(computedPerExpectationMaxMs, 5000); // At least 5s
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
totalMaxMs: finalTotalMaxMs,
|
|
119
|
+
learnMaxMs: finalLearnMaxMs,
|
|
120
|
+
observeMaxMs: finalObserveMaxMs,
|
|
121
|
+
detectMaxMs: finalDetectMaxMs,
|
|
122
|
+
perExpectationMaxMs: finalPerExpectationMaxMs,
|
|
123
|
+
mode,
|
|
124
|
+
framework,
|
|
125
|
+
expectationsCount,
|
|
126
|
+
projectSize,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Create a timeout wrapper that rejects after specified milliseconds
|
|
132
|
+
* @param {number} timeoutMs - Timeout in milliseconds
|
|
133
|
+
* @param {Promise} promise - Promise to wrap
|
|
134
|
+
* @param {string} phase - Phase name for error messages
|
|
135
|
+
* @returns {Promise} Promise that rejects on timeout
|
|
136
|
+
*/
|
|
137
|
+
export function withTimeout(timeoutMs, promise, phase = 'unknown') {
|
|
138
|
+
return Promise.race([
|
|
139
|
+
promise,
|
|
140
|
+
new Promise((_, reject) => {
|
|
141
|
+
setTimeout(() => {
|
|
142
|
+
reject(new Error(`Phase timeout: ${phase} exceeded ${timeoutMs}ms`));
|
|
143
|
+
}, timeoutMs);
|
|
144
|
+
}),
|
|
145
|
+
]);
|
|
146
|
+
}
|
|
147
|
+
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { existsSync, readdirSync, statSync } from 'fs';
|
|
2
|
+
import { extname, join, resolve } from 'path';
|
|
3
|
+
import { DataError } from './errors.js';
|
|
4
|
+
import { getSourceCodeRequirementBanner } from '../../verax/core/product-definition.js';
|
|
5
|
+
|
|
6
|
+
const CODE_EXTS = new Set(['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.html']);
|
|
7
|
+
|
|
8
|
+
function safeReaddir(dirPath) {
|
|
9
|
+
try {
|
|
10
|
+
return readdirSync(dirPath);
|
|
11
|
+
} catch {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function directoryHasCode(dirPath) {
|
|
17
|
+
const entries = safeReaddir(dirPath);
|
|
18
|
+
for (const entry of entries) {
|
|
19
|
+
const fullPath = join(dirPath, entry);
|
|
20
|
+
let stats;
|
|
21
|
+
try {
|
|
22
|
+
stats = statSync(fullPath);
|
|
23
|
+
} catch {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (stats.isFile() && CODE_EXTS.has(extname(entry).toLowerCase())) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (stats.isDirectory() && (entry === 'src' || entry === 'app')) {
|
|
32
|
+
const nested = safeReaddir(fullPath).slice(0, 50);
|
|
33
|
+
if (nested.some((name) => CODE_EXTS.has(extname(name).toLowerCase()))) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function assertHasLocalSource(srcPath) {
|
|
42
|
+
const resolved = resolve(srcPath);
|
|
43
|
+
const hasPackageJson = existsSync(join(resolved, 'package.json'));
|
|
44
|
+
const hasIndexHtml = existsSync(join(resolved, 'index.html'));
|
|
45
|
+
const hasCodeFiles = directoryHasCode(resolved);
|
|
46
|
+
|
|
47
|
+
if (!hasPackageJson && !hasIndexHtml && !hasCodeFiles) {
|
|
48
|
+
const banner = getSourceCodeRequirementBanner();
|
|
49
|
+
throw new DataError(
|
|
50
|
+
`${banner} Provide --src pointing to your repository so VERAX can analyze expectations. See docs/README.md for the canonical product contract.`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return { hasPackageJson, hasIndexHtml, hasCodeFiles };
|
|
55
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { atomicWriteJson } from './atomic-write.js';
|
|
2
|
-
import { resolve } from 'path';
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* Write summary.json with deterministic digest
|
|
@@ -8,6 +7,7 @@ import { resolve } from 'path';
|
|
|
8
7
|
*/
|
|
9
8
|
export function writeSummaryJson(summaryPath, summaryData, stats = {}) {
|
|
10
9
|
const payload = {
|
|
10
|
+
contractVersion: 1,
|
|
11
11
|
runId: summaryData.runId,
|
|
12
12
|
status: summaryData.status,
|
|
13
13
|
startedAt: summaryData.startedAt,
|
|
@@ -15,6 +15,18 @@ export function writeSummaryJson(summaryPath, summaryData, stats = {}) {
|
|
|
15
15
|
command: summaryData.command,
|
|
16
16
|
url: summaryData.url,
|
|
17
17
|
notes: summaryData.notes,
|
|
18
|
+
metrics: summaryData.metrics || {
|
|
19
|
+
learnMs: stats.learnMs || 0,
|
|
20
|
+
observeMs: stats.observeMs || 0,
|
|
21
|
+
detectMs: stats.detectMs || 0,
|
|
22
|
+
totalMs: stats.totalMs || 0,
|
|
23
|
+
},
|
|
24
|
+
findingsCounts: summaryData.findingsCounts || {
|
|
25
|
+
HIGH: stats.HIGH || 0,
|
|
26
|
+
MEDIUM: stats.MEDIUM || 0,
|
|
27
|
+
LOW: stats.LOW || 0,
|
|
28
|
+
UNKNOWN: stats.UNKNOWN || 0,
|
|
29
|
+
},
|
|
18
30
|
|
|
19
31
|
// Stable digest that should be identical across repeated runs on same input
|
|
20
32
|
digest: {
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 20 — Svelte Navigation Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects navigation promises in Svelte applications:
|
|
5
|
+
* - <a href="/path"> links
|
|
6
|
+
* - goto() calls from SvelteKit
|
|
7
|
+
* - programmatic navigation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { extractSvelteSFC, extractTemplateBindings, mapTemplateHandlersToScript } from './svelte-sfc-extractor.js';
|
|
11
|
+
import { parse } from '@babel/parser';
|
|
12
|
+
import traverse from '@babel/traverse';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Detect navigation promises in Svelte SFC
|
|
16
|
+
*
|
|
17
|
+
* @param {string} filePath - Path to .svelte file
|
|
18
|
+
* @param {string} content - Full file content
|
|
19
|
+
* @param {string} projectRoot - Project root directory
|
|
20
|
+
* @returns {Array} Array of navigation expectations
|
|
21
|
+
*/
|
|
22
|
+
export function detectSvelteNavigation(filePath, content, projectRoot) {
|
|
23
|
+
const expectations = [];
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const sfc = extractSvelteSFC(content);
|
|
27
|
+
const { scriptBlocks, markup } = sfc;
|
|
28
|
+
|
|
29
|
+
// Extract navigation from markup (links)
|
|
30
|
+
if (markup && markup.content) {
|
|
31
|
+
const linkRegex = /<a\s+[^>]*href=["']([^"']+)["'][^>]*>/gi;
|
|
32
|
+
let linkMatch;
|
|
33
|
+
while ((linkMatch = linkRegex.exec(markup.content)) !== null) {
|
|
34
|
+
const href = linkMatch[1];
|
|
35
|
+
// Skip external links and hash-only links
|
|
36
|
+
if (href.startsWith('http://') || href.startsWith('https://') || href.startsWith('#')) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const beforeMatch = markup.content.substring(0, linkMatch.index);
|
|
41
|
+
const line = markup.startLine + (beforeMatch.match(/\n/g) || []).length;
|
|
42
|
+
|
|
43
|
+
expectations.push({
|
|
44
|
+
type: 'navigation',
|
|
45
|
+
target: href,
|
|
46
|
+
context: 'markup',
|
|
47
|
+
sourceRef: {
|
|
48
|
+
file: filePath,
|
|
49
|
+
line,
|
|
50
|
+
snippet: linkMatch[0],
|
|
51
|
+
},
|
|
52
|
+
proof: 'PROVEN_EXPECTATION',
|
|
53
|
+
metadata: {
|
|
54
|
+
navigationType: 'link',
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Extract navigation from script blocks (goto, navigate, etc.)
|
|
61
|
+
for (const scriptBlock of scriptBlocks) {
|
|
62
|
+
if (!scriptBlock.content) continue;
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const ast = parse(scriptBlock.content, {
|
|
66
|
+
sourceType: 'module',
|
|
67
|
+
plugins: ['typescript', 'jsx'],
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
traverse.default(ast, {
|
|
71
|
+
CallExpression(path) {
|
|
72
|
+
const { node } = path;
|
|
73
|
+
|
|
74
|
+
// Detect goto() calls (SvelteKit)
|
|
75
|
+
if (
|
|
76
|
+
node.callee.type === 'Identifier' &&
|
|
77
|
+
node.callee.name === 'goto' &&
|
|
78
|
+
node.arguments.length > 0
|
|
79
|
+
) {
|
|
80
|
+
const arg = node.arguments[0];
|
|
81
|
+
let target = null;
|
|
82
|
+
|
|
83
|
+
if (arg.type === 'StringLiteral') {
|
|
84
|
+
target = arg.value;
|
|
85
|
+
} else if (arg.type === 'TemplateLiteral' && arg.quasis.length === 1) {
|
|
86
|
+
target = arg.quasis[0].value.raw;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (target && !target.startsWith('http://') && !target.startsWith('https://') && !target.startsWith('#')) {
|
|
90
|
+
const location = node.loc;
|
|
91
|
+
const line = scriptBlock.startLine + (location ? location.start.line - 1 : 0);
|
|
92
|
+
|
|
93
|
+
expectations.push({
|
|
94
|
+
type: 'navigation',
|
|
95
|
+
target,
|
|
96
|
+
context: 'goto',
|
|
97
|
+
sourceRef: {
|
|
98
|
+
file: filePath,
|
|
99
|
+
line,
|
|
100
|
+
snippet: scriptBlock.content.substring(
|
|
101
|
+
node.start - (ast.program.body[0]?.start || 0),
|
|
102
|
+
node.end - (ast.program.body[0]?.start || 0)
|
|
103
|
+
),
|
|
104
|
+
},
|
|
105
|
+
proof: arg.type === 'StringLiteral' ? 'PROVEN_EXPECTATION' : 'LIKELY_EXPECTATION',
|
|
106
|
+
metadata: {
|
|
107
|
+
navigationType: 'goto',
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Detect navigate() calls (if imported)
|
|
114
|
+
if (
|
|
115
|
+
node.callee.type === 'MemberExpression' &&
|
|
116
|
+
node.callee.property.name === 'navigate' &&
|
|
117
|
+
node.arguments.length > 0
|
|
118
|
+
) {
|
|
119
|
+
const arg = node.arguments[0];
|
|
120
|
+
let target = null;
|
|
121
|
+
|
|
122
|
+
if (arg.type === 'StringLiteral') {
|
|
123
|
+
target = arg.value;
|
|
124
|
+
} else if (arg.type === 'TemplateLiteral' && arg.quasis.length === 1) {
|
|
125
|
+
target = arg.quasis[0].value.raw;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (target && !target.startsWith('http://') && !target.startsWith('https://') && !target.startsWith('#')) {
|
|
129
|
+
const location = node.loc;
|
|
130
|
+
const line = scriptBlock.startLine + (location ? location.start.line - 1 : 0);
|
|
131
|
+
|
|
132
|
+
expectations.push({
|
|
133
|
+
type: 'navigation',
|
|
134
|
+
target,
|
|
135
|
+
context: 'navigate',
|
|
136
|
+
sourceRef: {
|
|
137
|
+
file: filePath,
|
|
138
|
+
line,
|
|
139
|
+
snippet: scriptBlock.content.substring(
|
|
140
|
+
node.start - (ast.program.body[0]?.start || 0),
|
|
141
|
+
node.end - (ast.program.body[0]?.start || 0)
|
|
142
|
+
),
|
|
143
|
+
},
|
|
144
|
+
proof: arg.type === 'StringLiteral' ? 'PROVEN_EXPECTATION' : 'LIKELY_EXPECTATION',
|
|
145
|
+
metadata: {
|
|
146
|
+
navigationType: 'navigate',
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
} catch (parseError) {
|
|
154
|
+
// Skip if parsing fails
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
} catch (error) {
|
|
158
|
+
// Skip if extraction fails
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return expectations;
|
|
162
|
+
}
|
|
163
|
+
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 20 — Svelte Network Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects network calls (fetch, axios) in Svelte component handlers and lifecycle functions.
|
|
5
|
+
* Reuses AST network detector but ensures it works with Svelte SFC script blocks.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { extractSvelteSFC, extractTemplateBindings, mapTemplateHandlersToScript } from './svelte-sfc-extractor.js';
|
|
9
|
+
import { detectNetworkCallsAST } from './ast-network-detector.js';
|
|
10
|
+
import { relative } from 'path';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Detect network promises in Svelte SFC
|
|
14
|
+
*
|
|
15
|
+
* @param {string} filePath - Path to .svelte file
|
|
16
|
+
* @param {string} content - Full file content
|
|
17
|
+
* @param {string} projectRoot - Project root directory
|
|
18
|
+
* @returns {Array} Array of network expectations
|
|
19
|
+
*/
|
|
20
|
+
export function detectSvelteNetwork(filePath, content, projectRoot) {
|
|
21
|
+
const expectations = [];
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const sfc = extractSvelteSFC(content);
|
|
25
|
+
const { scriptBlocks, markup } = sfc;
|
|
26
|
+
|
|
27
|
+
// Extract event handlers from markup to identify UI-bound handlers
|
|
28
|
+
const templateBindings = markup ? extractTemplateBindings(markup.content) : { eventHandlers: [] };
|
|
29
|
+
const mappedHandlers = scriptBlocks.length > 0 && templateBindings.eventHandlers.length > 0
|
|
30
|
+
? mapTemplateHandlersToScript(templateBindings.eventHandlers, scriptBlocks[0].content)
|
|
31
|
+
: [];
|
|
32
|
+
|
|
33
|
+
const uiBoundHandlers = new Set(mappedHandlers.map(h => h.handler));
|
|
34
|
+
|
|
35
|
+
// Process each script block
|
|
36
|
+
for (const scriptBlock of scriptBlocks) {
|
|
37
|
+
if (!scriptBlock.content) continue;
|
|
38
|
+
|
|
39
|
+
// Use AST network detector on script content
|
|
40
|
+
const networkCalls = detectNetworkCallsAST(scriptBlock.content, filePath, relative(projectRoot, filePath));
|
|
41
|
+
|
|
42
|
+
// Filter and enhance network calls
|
|
43
|
+
for (const networkCall of networkCalls) {
|
|
44
|
+
// Check if this is in a UI-bound handler
|
|
45
|
+
const isUIBound = networkCall.context && uiBoundHandlers.has(networkCall.context);
|
|
46
|
+
|
|
47
|
+
// Skip analytics-only calls (filtered by guardrails later)
|
|
48
|
+
if (networkCall.target && (
|
|
49
|
+
networkCall.target.includes('/api/analytics') ||
|
|
50
|
+
networkCall.target.includes('/api/track') ||
|
|
51
|
+
networkCall.target.includes('/api/beacon')
|
|
52
|
+
)) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
expectations.push({
|
|
57
|
+
type: 'network',
|
|
58
|
+
target: networkCall.target,
|
|
59
|
+
method: networkCall.method || 'GET',
|
|
60
|
+
context: networkCall.context || 'component',
|
|
61
|
+
sourceRef: {
|
|
62
|
+
file: filePath,
|
|
63
|
+
line: networkCall.line || scriptBlock.startLine,
|
|
64
|
+
snippet: networkCall.snippet || '',
|
|
65
|
+
},
|
|
66
|
+
proof: networkCall.proof || 'LIKELY_EXPECTATION',
|
|
67
|
+
metadata: {
|
|
68
|
+
isUIBound,
|
|
69
|
+
handlerContext: networkCall.context,
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
} catch (error) {
|
|
75
|
+
// Skip if extraction fails
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return expectations;
|
|
79
|
+
}
|
|
80
|
+
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 20 — Svelte SFC (Single File Component) Extractor
|
|
3
|
+
*
|
|
4
|
+
* Extracts <script>, <script context="module">, and markup content from .svelte files.
|
|
5
|
+
* Deterministic and robust (no external runtime execution).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* PHASE 20: Extract Svelte SFC blocks
|
|
10
|
+
*
|
|
11
|
+
* @param {string} content - Full .svelte file content
|
|
12
|
+
* @param {string} filePath - Path to the .svelte file (for context)
|
|
13
|
+
* @returns {Object} { scriptBlocks: [{content, lang, startLine, isModule}], markup: {content, startLine} }
|
|
14
|
+
*/
|
|
15
|
+
export function extractSvelteSFC(content) {
|
|
16
|
+
const scriptBlocks = [];
|
|
17
|
+
let markup = null;
|
|
18
|
+
|
|
19
|
+
// Extract <script> blocks (including <script context="module">)
|
|
20
|
+
const scriptRegex = /<script(?:\s+context=["']module["'])?(?:\s+lang=["']([^"']+)["'])?[^>]*>([\s\S]*?)<\/script>/gi;
|
|
21
|
+
let scriptMatch;
|
|
22
|
+
|
|
23
|
+
while ((scriptMatch = scriptRegex.exec(content)) !== null) {
|
|
24
|
+
const isModule = scriptMatch[0].includes('context="module"') || scriptMatch[0].includes("context='module'");
|
|
25
|
+
const lang = scriptMatch[1] || 'js';
|
|
26
|
+
const scriptContent = scriptMatch[2];
|
|
27
|
+
|
|
28
|
+
// Calculate start line
|
|
29
|
+
const beforeMatch = content.substring(0, scriptMatch.index);
|
|
30
|
+
const startLine = (beforeMatch.match(/\n/g) || []).length + 1;
|
|
31
|
+
|
|
32
|
+
scriptBlocks.push({
|
|
33
|
+
content: scriptContent.trim(),
|
|
34
|
+
lang: lang.toLowerCase(),
|
|
35
|
+
startLine,
|
|
36
|
+
isModule,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Extract markup (everything outside script/style tags)
|
|
41
|
+
// Svelte markup is the template content
|
|
42
|
+
const styleRegex = /<style[^>]*>[\s\S]*?<\/style>/gi;
|
|
43
|
+
const allScriptRegex = /<script[^>]*>[\s\S]*?<\/script>/gi;
|
|
44
|
+
|
|
45
|
+
let markupContent = content;
|
|
46
|
+
// Remove style blocks
|
|
47
|
+
markupContent = markupContent.replace(styleRegex, '');
|
|
48
|
+
// Remove script blocks
|
|
49
|
+
markupContent = markupContent.replace(allScriptRegex, '');
|
|
50
|
+
|
|
51
|
+
// Find first non-whitespace line for markup
|
|
52
|
+
const lines = content.split('\n');
|
|
53
|
+
let markupStartLine = 1;
|
|
54
|
+
for (let i = 0; i < lines.length; i++) {
|
|
55
|
+
const line = lines[i];
|
|
56
|
+
if (line.trim() && !line.trim().startsWith('<script') && !line.trim().startsWith('<style')) {
|
|
57
|
+
markupStartLine = i + 1;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (markupContent.trim()) {
|
|
63
|
+
markup = {
|
|
64
|
+
content: markupContent.trim(),
|
|
65
|
+
startLine: markupStartLine,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
scriptBlocks,
|
|
71
|
+
markup,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Extract template bindings from Svelte markup
|
|
77
|
+
* Detects reactive statements, event handlers, and bindings
|
|
78
|
+
*
|
|
79
|
+
* @param {string} markupContent - Svelte markup content
|
|
80
|
+
* @returns {Object} { reactiveStatements: [], eventHandlers: [], bindings: [] }
|
|
81
|
+
*/
|
|
82
|
+
export function extractTemplateBindings(markupContent) {
|
|
83
|
+
const reactiveStatements = [];
|
|
84
|
+
const eventHandlers = [];
|
|
85
|
+
const bindings = [];
|
|
86
|
+
|
|
87
|
+
// Extract reactive statements: $: statements
|
|
88
|
+
const reactiveRegex = /\$:\s*([^;]+);/g;
|
|
89
|
+
let reactiveMatch;
|
|
90
|
+
while ((reactiveMatch = reactiveRegex.exec(markupContent)) !== null) {
|
|
91
|
+
reactiveStatements.push({
|
|
92
|
+
statement: reactiveMatch[1].trim(),
|
|
93
|
+
line: (markupContent.substring(0, reactiveMatch.index).match(/\n/g) || []).length + 1,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Extract event handlers: on:click, on:submit, etc.
|
|
98
|
+
const eventHandlerRegex = /on:(\w+)=["']([^"']+)["']/g;
|
|
99
|
+
let handlerMatch;
|
|
100
|
+
while ((handlerMatch = eventHandlerRegex.exec(markupContent)) !== null) {
|
|
101
|
+
eventHandlers.push({
|
|
102
|
+
event: handlerMatch[1],
|
|
103
|
+
handler: handlerMatch[2],
|
|
104
|
+
line: (markupContent.substring(0, handlerMatch.index).match(/\n/g) || []).length + 1,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Extract bindings: bind:value, bind:checked, etc.
|
|
109
|
+
const bindingRegex = /bind:(\w+)=["']([^"']+)["']/g;
|
|
110
|
+
let bindingMatch;
|
|
111
|
+
while ((bindingMatch = bindingRegex.exec(markupContent)) !== null) {
|
|
112
|
+
bindings.push({
|
|
113
|
+
property: bindingMatch[1],
|
|
114
|
+
variable: bindingMatch[2],
|
|
115
|
+
line: (markupContent.substring(0, bindingMatch.index).match(/\n/g) || []).length + 1,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
reactiveStatements,
|
|
121
|
+
eventHandlers,
|
|
122
|
+
bindings,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Map template handlers to script functions
|
|
128
|
+
* Helps identify which handlers are UI-bound
|
|
129
|
+
*
|
|
130
|
+
* @param {Array} eventHandlers - Event handlers from template
|
|
131
|
+
* @param {string} scriptContent - Script block content
|
|
132
|
+
* @returns {Array} Mapped handlers with function references
|
|
133
|
+
*/
|
|
134
|
+
export function mapTemplateHandlersToScript(eventHandlers, scriptContent) {
|
|
135
|
+
return eventHandlers.map(handler => {
|
|
136
|
+
// Try to find function definition in script
|
|
137
|
+
const functionRegex = new RegExp(`(?:function\\s+${handler.handler}|const\\s+${handler.handler}\\s*=\\s*[^(]*\\(|${handler.handler}\\s*=\\s*[^(]*\\(|export\\s+function\\s+${handler.handler})`, 'g');
|
|
138
|
+
const functionMatch = functionRegex.exec(scriptContent);
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
...handler,
|
|
142
|
+
functionFound: !!functionMatch,
|
|
143
|
+
functionLine: functionMatch ? (scriptContent.substring(0, functionMatch.index).match(/\n/g) || []).length + 1 : null,
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|