@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.
Files changed (217) hide show
  1. package/README.md +14 -18
  2. package/bin/verax.js +7 -0
  3. package/package.json +15 -5
  4. package/src/cli/commands/baseline.js +104 -0
  5. package/src/cli/commands/default.js +323 -111
  6. package/src/cli/commands/doctor.js +36 -4
  7. package/src/cli/commands/ga.js +243 -0
  8. package/src/cli/commands/gates.js +95 -0
  9. package/src/cli/commands/inspect.js +131 -2
  10. package/src/cli/commands/release-check.js +213 -0
  11. package/src/cli/commands/run.js +498 -103
  12. package/src/cli/commands/security-check.js +211 -0
  13. package/src/cli/commands/truth.js +114 -0
  14. package/src/cli/entry.js +305 -68
  15. package/src/cli/util/angular-component-extractor.js +179 -0
  16. package/src/cli/util/angular-navigation-detector.js +141 -0
  17. package/src/cli/util/angular-network-detector.js +161 -0
  18. package/src/cli/util/angular-state-detector.js +162 -0
  19. package/src/cli/util/ast-interactive-detector.js +546 -0
  20. package/src/cli/util/ast-network-detector.js +603 -0
  21. package/src/cli/util/ast-usestate-detector.js +602 -0
  22. package/src/cli/util/bootstrap-guard.js +86 -0
  23. package/src/cli/util/detection-engine.js +4 -3
  24. package/src/cli/util/determinism-runner.js +123 -0
  25. package/src/cli/util/determinism-writer.js +129 -0
  26. package/src/cli/util/env-url.js +4 -0
  27. package/src/cli/util/events.js +76 -0
  28. package/src/cli/util/expectation-extractor.js +380 -74
  29. package/src/cli/util/findings-writer.js +126 -15
  30. package/src/cli/util/learn-writer.js +3 -1
  31. package/src/cli/util/observation-engine.js +69 -23
  32. package/src/cli/util/observe-writer.js +3 -1
  33. package/src/cli/util/paths.js +6 -14
  34. package/src/cli/util/project-discovery.js +23 -0
  35. package/src/cli/util/project-writer.js +3 -1
  36. package/src/cli/util/redact.js +2 -2
  37. package/src/cli/util/run-resolver.js +64 -0
  38. package/src/cli/util/runtime-budget.js +147 -0
  39. package/src/cli/util/source-requirement.js +55 -0
  40. package/src/cli/util/summary-writer.js +13 -1
  41. package/src/cli/util/svelte-navigation-detector.js +163 -0
  42. package/src/cli/util/svelte-network-detector.js +80 -0
  43. package/src/cli/util/svelte-sfc-extractor.js +147 -0
  44. package/src/cli/util/svelte-state-detector.js +243 -0
  45. package/src/cli/util/vue-navigation-detector.js +177 -0
  46. package/src/cli/util/vue-sfc-extractor.js +162 -0
  47. package/src/cli/util/vue-state-detector.js +215 -0
  48. package/src/types/global.d.ts +28 -0
  49. package/src/types/ts-ast.d.ts +24 -0
  50. package/src/verax/cli/doctor.js +2 -2
  51. package/src/verax/cli/finding-explainer.js +56 -3
  52. package/src/verax/cli/init.js +1 -1
  53. package/src/verax/cli/url-safety.js +12 -2
  54. package/src/verax/cli/wizard.js +13 -2
  55. package/src/verax/core/artifacts/registry.js +154 -0
  56. package/src/verax/core/artifacts/verifier.js +980 -0
  57. package/src/verax/core/baseline/baseline.enforcer.js +137 -0
  58. package/src/verax/core/baseline/baseline.snapshot.js +231 -0
  59. package/src/verax/core/budget-engine.js +1 -1
  60. package/src/verax/core/capabilities/gates.js +499 -0
  61. package/src/verax/core/capabilities/registry.js +475 -0
  62. package/src/verax/core/confidence/confidence-compute.js +137 -0
  63. package/src/verax/core/confidence/confidence-invariants.js +234 -0
  64. package/src/verax/core/confidence/confidence-report-writer.js +112 -0
  65. package/src/verax/core/confidence/confidence-weights.js +44 -0
  66. package/src/verax/core/confidence/confidence.defaults.js +65 -0
  67. package/src/verax/core/confidence/confidence.loader.js +79 -0
  68. package/src/verax/core/confidence/confidence.schema.js +94 -0
  69. package/src/verax/core/confidence-engine-refactor.js +484 -0
  70. package/src/verax/core/confidence-engine.js +486 -0
  71. package/src/verax/core/confidence-engine.js.backup +471 -0
  72. package/src/verax/core/contracts/index.js +29 -0
  73. package/src/verax/core/contracts/types.js +185 -0
  74. package/src/verax/core/contracts/validators.js +381 -0
  75. package/src/verax/core/decision-snapshot.js +31 -4
  76. package/src/verax/core/decisions/decision.trace.js +276 -0
  77. package/src/verax/core/determinism/contract-writer.js +89 -0
  78. package/src/verax/core/determinism/contract.js +139 -0
  79. package/src/verax/core/determinism/diff.js +364 -0
  80. package/src/verax/core/determinism/engine.js +221 -0
  81. package/src/verax/core/determinism/finding-identity.js +148 -0
  82. package/src/verax/core/determinism/normalize.js +438 -0
  83. package/src/verax/core/determinism/report-writer.js +92 -0
  84. package/src/verax/core/determinism/run-fingerprint.js +118 -0
  85. package/src/verax/core/determinism-model.js +35 -6
  86. package/src/verax/core/dynamic-route-intelligence.js +528 -0
  87. package/src/verax/core/evidence/evidence-capture-service.js +307 -0
  88. package/src/verax/core/evidence/evidence-intent-ledger.js +165 -0
  89. package/src/verax/core/evidence-builder.js +487 -0
  90. package/src/verax/core/execution-mode-context.js +77 -0
  91. package/src/verax/core/execution-mode-detector.js +190 -0
  92. package/src/verax/core/failures/exit-codes.js +86 -0
  93. package/src/verax/core/failures/failure-summary.js +76 -0
  94. package/src/verax/core/failures/failure.factory.js +225 -0
  95. package/src/verax/core/failures/failure.ledger.js +132 -0
  96. package/src/verax/core/failures/failure.types.js +196 -0
  97. package/src/verax/core/failures/index.js +10 -0
  98. package/src/verax/core/ga/ga-report-writer.js +43 -0
  99. package/src/verax/core/ga/ga.artifact.js +49 -0
  100. package/src/verax/core/ga/ga.contract.js +434 -0
  101. package/src/verax/core/ga/ga.enforcer.js +86 -0
  102. package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
  103. package/src/verax/core/guardrails/policy.defaults.js +210 -0
  104. package/src/verax/core/guardrails/policy.loader.js +83 -0
  105. package/src/verax/core/guardrails/policy.schema.js +110 -0
  106. package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
  107. package/src/verax/core/guardrails-engine.js +505 -0
  108. package/src/verax/core/incremental-store.js +15 -7
  109. package/src/verax/core/observe/run-timeline.js +316 -0
  110. package/src/verax/core/perf/perf.contract.js +186 -0
  111. package/src/verax/core/perf/perf.display.js +65 -0
  112. package/src/verax/core/perf/perf.enforcer.js +91 -0
  113. package/src/verax/core/perf/perf.monitor.js +209 -0
  114. package/src/verax/core/perf/perf.report.js +198 -0
  115. package/src/verax/core/pipeline-tracker.js +238 -0
  116. package/src/verax/core/product-definition.js +127 -0
  117. package/src/verax/core/release/provenance.builder.js +271 -0
  118. package/src/verax/core/release/release-report-writer.js +40 -0
  119. package/src/verax/core/release/release.enforcer.js +159 -0
  120. package/src/verax/core/release/reproducibility.check.js +221 -0
  121. package/src/verax/core/release/sbom.builder.js +283 -0
  122. package/src/verax/core/replay-validator.js +4 -4
  123. package/src/verax/core/replay.js +1 -1
  124. package/src/verax/core/report/cross-index.js +192 -0
  125. package/src/verax/core/report/human-summary.js +222 -0
  126. package/src/verax/core/route-intelligence.js +419 -0
  127. package/src/verax/core/security/secrets.scan.js +326 -0
  128. package/src/verax/core/security/security-report.js +50 -0
  129. package/src/verax/core/security/security.enforcer.js +124 -0
  130. package/src/verax/core/security/supplychain.defaults.json +38 -0
  131. package/src/verax/core/security/supplychain.policy.js +326 -0
  132. package/src/verax/core/security/vuln.scan.js +265 -0
  133. package/src/verax/core/silence-impact.js +1 -1
  134. package/src/verax/core/silence-model.js +9 -7
  135. package/src/verax/core/truth/truth.certificate.js +250 -0
  136. package/src/verax/core/ui-feedback-intelligence.js +515 -0
  137. package/src/verax/detect/comparison.js +8 -3
  138. package/src/verax/detect/confidence-engine.js +645 -57
  139. package/src/verax/detect/confidence-helper.js +33 -0
  140. package/src/verax/detect/detection-engine.js +19 -2
  141. package/src/verax/detect/dynamic-route-findings.js +335 -0
  142. package/src/verax/detect/evidence-index.js +15 -65
  143. package/src/verax/detect/expectation-chain-detector.js +417 -0
  144. package/src/verax/detect/expectation-model.js +56 -3
  145. package/src/verax/detect/explanation-helpers.js +1 -1
  146. package/src/verax/detect/finding-detector.js +2 -2
  147. package/src/verax/detect/findings-writer.js +149 -20
  148. package/src/verax/detect/flow-detector.js +4 -4
  149. package/src/verax/detect/index.js +265 -15
  150. package/src/verax/detect/interactive-findings.js +3 -4
  151. package/src/verax/detect/journey-stall-detector.js +558 -0
  152. package/src/verax/detect/route-findings.js +218 -0
  153. package/src/verax/detect/signal-mapper.js +2 -2
  154. package/src/verax/detect/skip-classifier.js +4 -4
  155. package/src/verax/detect/ui-feedback-findings.js +207 -0
  156. package/src/verax/detect/verdict-engine.js +61 -9
  157. package/src/verax/detect/view-switch-correlator.js +242 -0
  158. package/src/verax/flow/flow-engine.js +3 -2
  159. package/src/verax/flow/flow-spec.js +1 -2
  160. package/src/verax/index.js +413 -33
  161. package/src/verax/intel/effect-detector.js +1 -1
  162. package/src/verax/intel/index.js +2 -2
  163. package/src/verax/intel/route-extractor.js +3 -3
  164. package/src/verax/intel/vue-navigation-extractor.js +81 -18
  165. package/src/verax/intel/vue-router-extractor.js +4 -2
  166. package/src/verax/learn/action-contract-extractor.js +684 -66
  167. package/src/verax/learn/ast-contract-extractor.js +53 -1
  168. package/src/verax/learn/index.js +36 -2
  169. package/src/verax/learn/manifest-writer.js +28 -14
  170. package/src/verax/learn/route-extractor.js +1 -1
  171. package/src/verax/learn/route-validator.js +12 -8
  172. package/src/verax/learn/state-extractor.js +1 -1
  173. package/src/verax/learn/static-extractor-navigation.js +1 -1
  174. package/src/verax/learn/static-extractor-validation.js +2 -2
  175. package/src/verax/learn/static-extractor.js +8 -7
  176. package/src/verax/learn/ts-contract-resolver.js +14 -12
  177. package/src/verax/observe/browser.js +22 -3
  178. package/src/verax/observe/console-sensor.js +2 -2
  179. package/src/verax/observe/expectation-executor.js +2 -1
  180. package/src/verax/observe/focus-sensor.js +1 -1
  181. package/src/verax/observe/human-driver.js +29 -10
  182. package/src/verax/observe/index.js +92 -844
  183. package/src/verax/observe/interaction-discovery.js +27 -15
  184. package/src/verax/observe/interaction-runner.js +31 -14
  185. package/src/verax/observe/loading-sensor.js +6 -0
  186. package/src/verax/observe/navigation-sensor.js +1 -1
  187. package/src/verax/observe/observe-context.js +205 -0
  188. package/src/verax/observe/observe-helpers.js +191 -0
  189. package/src/verax/observe/observe-runner.js +226 -0
  190. package/src/verax/observe/observers/budget-observer.js +185 -0
  191. package/src/verax/observe/observers/console-observer.js +102 -0
  192. package/src/verax/observe/observers/coverage-observer.js +107 -0
  193. package/src/verax/observe/observers/interaction-observer.js +471 -0
  194. package/src/verax/observe/observers/navigation-observer.js +132 -0
  195. package/src/verax/observe/observers/network-observer.js +87 -0
  196. package/src/verax/observe/observers/safety-observer.js +82 -0
  197. package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
  198. package/src/verax/observe/settle.js +1 -0
  199. package/src/verax/observe/state-sensor.js +8 -4
  200. package/src/verax/observe/state-ui-sensor.js +7 -1
  201. package/src/verax/observe/traces-writer.js +27 -16
  202. package/src/verax/observe/ui-feedback-detector.js +742 -0
  203. package/src/verax/observe/ui-signal-sensor.js +155 -2
  204. package/src/verax/scan-summary-writer.js +46 -9
  205. package/src/verax/shared/artifact-manager.js +9 -6
  206. package/src/verax/shared/budget-profiles.js +2 -2
  207. package/src/verax/shared/caching.js +1 -1
  208. package/src/verax/shared/config-loader.js +1 -2
  209. package/src/verax/shared/css-spinner-rules.js +204 -0
  210. package/src/verax/shared/dynamic-route-utils.js +12 -6
  211. package/src/verax/shared/retry-policy.js +1 -6
  212. package/src/verax/shared/root-artifacts.js +1 -1
  213. package/src/verax/shared/view-switch-rules.js +208 -0
  214. package/src/verax/shared/zip-artifacts.js +1 -0
  215. package/src/verax/validate/context-validator.js +1 -1
  216. package/src/verax/observe/index.js.backup +0 -1
  217. 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
+