@veraxhq/verax 0.2.1 → 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 (152) hide show
  1. package/README.md +14 -18
  2. package/bin/verax.js +7 -0
  3. package/package.json +3 -3
  4. package/src/cli/commands/baseline.js +104 -0
  5. package/src/cli/commands/default.js +79 -25
  6. package/src/cli/commands/ga.js +243 -0
  7. package/src/cli/commands/gates.js +95 -0
  8. package/src/cli/commands/inspect.js +131 -2
  9. package/src/cli/commands/release-check.js +213 -0
  10. package/src/cli/commands/run.js +246 -35
  11. package/src/cli/commands/security-check.js +211 -0
  12. package/src/cli/commands/truth.js +114 -0
  13. package/src/cli/entry.js +304 -67
  14. package/src/cli/util/angular-component-extractor.js +179 -0
  15. package/src/cli/util/angular-navigation-detector.js +141 -0
  16. package/src/cli/util/angular-network-detector.js +161 -0
  17. package/src/cli/util/angular-state-detector.js +162 -0
  18. package/src/cli/util/ast-interactive-detector.js +546 -0
  19. package/src/cli/util/ast-network-detector.js +603 -0
  20. package/src/cli/util/ast-usestate-detector.js +602 -0
  21. package/src/cli/util/bootstrap-guard.js +86 -0
  22. package/src/cli/util/determinism-runner.js +123 -0
  23. package/src/cli/util/determinism-writer.js +129 -0
  24. package/src/cli/util/env-url.js +4 -0
  25. package/src/cli/util/expectation-extractor.js +369 -73
  26. package/src/cli/util/findings-writer.js +126 -16
  27. package/src/cli/util/learn-writer.js +3 -1
  28. package/src/cli/util/observe-writer.js +3 -1
  29. package/src/cli/util/paths.js +3 -12
  30. package/src/cli/util/project-discovery.js +3 -0
  31. package/src/cli/util/project-writer.js +3 -1
  32. package/src/cli/util/run-resolver.js +64 -0
  33. package/src/cli/util/source-requirement.js +55 -0
  34. package/src/cli/util/summary-writer.js +1 -0
  35. package/src/cli/util/svelte-navigation-detector.js +163 -0
  36. package/src/cli/util/svelte-network-detector.js +80 -0
  37. package/src/cli/util/svelte-sfc-extractor.js +147 -0
  38. package/src/cli/util/svelte-state-detector.js +243 -0
  39. package/src/cli/util/vue-navigation-detector.js +177 -0
  40. package/src/cli/util/vue-sfc-extractor.js +162 -0
  41. package/src/cli/util/vue-state-detector.js +215 -0
  42. package/src/verax/cli/finding-explainer.js +56 -3
  43. package/src/verax/core/artifacts/registry.js +154 -0
  44. package/src/verax/core/artifacts/verifier.js +980 -0
  45. package/src/verax/core/baseline/baseline.enforcer.js +137 -0
  46. package/src/verax/core/baseline/baseline.snapshot.js +231 -0
  47. package/src/verax/core/capabilities/gates.js +499 -0
  48. package/src/verax/core/capabilities/registry.js +475 -0
  49. package/src/verax/core/confidence/confidence-compute.js +137 -0
  50. package/src/verax/core/confidence/confidence-invariants.js +234 -0
  51. package/src/verax/core/confidence/confidence-report-writer.js +112 -0
  52. package/src/verax/core/confidence/confidence-weights.js +44 -0
  53. package/src/verax/core/confidence/confidence.defaults.js +65 -0
  54. package/src/verax/core/confidence/confidence.loader.js +79 -0
  55. package/src/verax/core/confidence/confidence.schema.js +94 -0
  56. package/src/verax/core/confidence-engine-refactor.js +484 -0
  57. package/src/verax/core/confidence-engine.js +486 -0
  58. package/src/verax/core/confidence-engine.js.backup +471 -0
  59. package/src/verax/core/contracts/index.js +29 -0
  60. package/src/verax/core/contracts/types.js +185 -0
  61. package/src/verax/core/contracts/validators.js +381 -0
  62. package/src/verax/core/decision-snapshot.js +30 -3
  63. package/src/verax/core/decisions/decision.trace.js +276 -0
  64. package/src/verax/core/determinism/contract-writer.js +89 -0
  65. package/src/verax/core/determinism/contract.js +139 -0
  66. package/src/verax/core/determinism/diff.js +364 -0
  67. package/src/verax/core/determinism/engine.js +221 -0
  68. package/src/verax/core/determinism/finding-identity.js +148 -0
  69. package/src/verax/core/determinism/normalize.js +438 -0
  70. package/src/verax/core/determinism/report-writer.js +92 -0
  71. package/src/verax/core/determinism/run-fingerprint.js +118 -0
  72. package/src/verax/core/dynamic-route-intelligence.js +528 -0
  73. package/src/verax/core/evidence/evidence-capture-service.js +307 -0
  74. package/src/verax/core/evidence/evidence-intent-ledger.js +165 -0
  75. package/src/verax/core/evidence-builder.js +487 -0
  76. package/src/verax/core/execution-mode-context.js +77 -0
  77. package/src/verax/core/execution-mode-detector.js +190 -0
  78. package/src/verax/core/failures/exit-codes.js +86 -0
  79. package/src/verax/core/failures/failure-summary.js +76 -0
  80. package/src/verax/core/failures/failure.factory.js +225 -0
  81. package/src/verax/core/failures/failure.ledger.js +132 -0
  82. package/src/verax/core/failures/failure.types.js +196 -0
  83. package/src/verax/core/failures/index.js +10 -0
  84. package/src/verax/core/ga/ga-report-writer.js +43 -0
  85. package/src/verax/core/ga/ga.artifact.js +49 -0
  86. package/src/verax/core/ga/ga.contract.js +434 -0
  87. package/src/verax/core/ga/ga.enforcer.js +86 -0
  88. package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
  89. package/src/verax/core/guardrails/policy.defaults.js +210 -0
  90. package/src/verax/core/guardrails/policy.loader.js +83 -0
  91. package/src/verax/core/guardrails/policy.schema.js +110 -0
  92. package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
  93. package/src/verax/core/guardrails-engine.js +505 -0
  94. package/src/verax/core/observe/run-timeline.js +316 -0
  95. package/src/verax/core/perf/perf.contract.js +186 -0
  96. package/src/verax/core/perf/perf.display.js +65 -0
  97. package/src/verax/core/perf/perf.enforcer.js +91 -0
  98. package/src/verax/core/perf/perf.monitor.js +209 -0
  99. package/src/verax/core/perf/perf.report.js +198 -0
  100. package/src/verax/core/pipeline-tracker.js +238 -0
  101. package/src/verax/core/product-definition.js +127 -0
  102. package/src/verax/core/release/provenance.builder.js +271 -0
  103. package/src/verax/core/release/release-report-writer.js +40 -0
  104. package/src/verax/core/release/release.enforcer.js +159 -0
  105. package/src/verax/core/release/reproducibility.check.js +221 -0
  106. package/src/verax/core/release/sbom.builder.js +283 -0
  107. package/src/verax/core/report/cross-index.js +192 -0
  108. package/src/verax/core/report/human-summary.js +222 -0
  109. package/src/verax/core/route-intelligence.js +419 -0
  110. package/src/verax/core/security/secrets.scan.js +326 -0
  111. package/src/verax/core/security/security-report.js +50 -0
  112. package/src/verax/core/security/security.enforcer.js +124 -0
  113. package/src/verax/core/security/supplychain.defaults.json +38 -0
  114. package/src/verax/core/security/supplychain.policy.js +326 -0
  115. package/src/verax/core/security/vuln.scan.js +265 -0
  116. package/src/verax/core/truth/truth.certificate.js +250 -0
  117. package/src/verax/core/ui-feedback-intelligence.js +515 -0
  118. package/src/verax/detect/confidence-engine.js +628 -40
  119. package/src/verax/detect/confidence-helper.js +33 -0
  120. package/src/verax/detect/detection-engine.js +18 -1
  121. package/src/verax/detect/dynamic-route-findings.js +335 -0
  122. package/src/verax/detect/expectation-chain-detector.js +417 -0
  123. package/src/verax/detect/expectation-model.js +3 -1
  124. package/src/verax/detect/findings-writer.js +141 -5
  125. package/src/verax/detect/index.js +229 -5
  126. package/src/verax/detect/journey-stall-detector.js +558 -0
  127. package/src/verax/detect/route-findings.js +218 -0
  128. package/src/verax/detect/ui-feedback-findings.js +207 -0
  129. package/src/verax/detect/verdict-engine.js +57 -3
  130. package/src/verax/detect/view-switch-correlator.js +242 -0
  131. package/src/verax/index.js +413 -45
  132. package/src/verax/learn/action-contract-extractor.js +682 -64
  133. package/src/verax/learn/route-validator.js +4 -1
  134. package/src/verax/observe/index.js +88 -843
  135. package/src/verax/observe/interaction-runner.js +25 -8
  136. package/src/verax/observe/observe-context.js +205 -0
  137. package/src/verax/observe/observe-helpers.js +191 -0
  138. package/src/verax/observe/observe-runner.js +226 -0
  139. package/src/verax/observe/observers/budget-observer.js +185 -0
  140. package/src/verax/observe/observers/console-observer.js +102 -0
  141. package/src/verax/observe/observers/coverage-observer.js +107 -0
  142. package/src/verax/observe/observers/interaction-observer.js +471 -0
  143. package/src/verax/observe/observers/navigation-observer.js +132 -0
  144. package/src/verax/observe/observers/network-observer.js +87 -0
  145. package/src/verax/observe/observers/safety-observer.js +82 -0
  146. package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
  147. package/src/verax/observe/ui-feedback-detector.js +742 -0
  148. package/src/verax/observe/ui-signal-sensor.js +148 -2
  149. package/src/verax/scan-summary-writer.js +42 -8
  150. package/src/verax/shared/artifact-manager.js +8 -5
  151. package/src/verax/shared/css-spinner-rules.js +204 -0
  152. package/src/verax/shared/view-switch-rules.js +208 -0
@@ -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
+
@@ -0,0 +1,243 @@
1
+ /**
2
+ * PHASE 20 — Svelte State Detector
3
+ *
4
+ * Detects state mutations (reactive stores, assignments) in Svelte components.
5
+ * Only emits state promises if state is user-visible (used in markup bindings).
6
+ */
7
+
8
+ import { extractSvelteSFC, extractTemplateBindings } from './svelte-sfc-extractor.js';
9
+ import { parse } from '@babel/parser';
10
+ import traverse from '@babel/traverse';
11
+
12
+ /**
13
+ * Detect state 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 state expectations
19
+ */
20
+ export function detectSvelteState(filePath, content, projectRoot) {
21
+ const expectations = [];
22
+
23
+ try {
24
+ const sfc = extractSvelteSFC(content);
25
+ const { scriptBlocks, markup } = sfc;
26
+
27
+ // Extract template bindings to identify user-visible state
28
+ const templateBindings = markup ? extractTemplateBindings(markup.content) : { bindings: [], reactiveStatements: [] };
29
+
30
+ // Collect all state variables used in template
31
+ const templateStateVars = new Set();
32
+
33
+ // From bindings: bind:value="count"
34
+ templateBindings.bindings.forEach(binding => {
35
+ templateStateVars.add(binding.variable);
36
+ });
37
+
38
+ // From reactive statements: $: doubled = count * 2
39
+ templateBindings.reactiveStatements.forEach(stmt => {
40
+ // Extract variable names from reactive statements
41
+ const varMatch = stmt.statement.match(/^\s*(\w+)\s*=/);
42
+ if (varMatch) {
43
+ templateStateVars.add(varMatch[1]);
44
+ }
45
+ });
46
+
47
+ // From markup: {count}, {#if isOpen}, etc.
48
+ if (markup && markup.content) {
49
+ // Extract {variable} patterns
50
+ const varPattern = /\{([a-zA-Z_$][a-zA-Z0-9_$]*)\}/g;
51
+ let varMatch;
52
+ while ((varMatch = varPattern.exec(markup.content)) !== null) {
53
+ templateStateVars.add(varMatch[1]);
54
+ }
55
+
56
+ // Extract {#if variable} patterns
57
+ const ifPattern = /\{#if\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\}/g;
58
+ let ifMatch;
59
+ while ((ifMatch = ifPattern.exec(markup.content)) !== null) {
60
+ templateStateVars.add(ifMatch[1]);
61
+ }
62
+ }
63
+
64
+ // Process script blocks to find state mutations
65
+ for (const scriptBlock of scriptBlocks) {
66
+ if (!scriptBlock.content) continue;
67
+
68
+ try {
69
+ const ast = parse(scriptBlock.content, {
70
+ sourceType: 'module',
71
+ plugins: ['typescript', 'jsx'],
72
+ });
73
+
74
+ // Track reactive store declarations
75
+ const reactiveStores = new Map();
76
+
77
+ traverse.default(ast, {
78
+ // Detect reactive store declarations: $store, writable(), readable()
79
+ VariableDeclarator(path) {
80
+ const { node } = path;
81
+ if (node.init) {
82
+ // Detect writable() stores
83
+ if (
84
+ node.init.type === 'CallExpression' &&
85
+ node.init.callee.name === 'writable'
86
+ ) {
87
+ const storeName = node.id.name;
88
+ reactiveStores.set(storeName, 'writable');
89
+ }
90
+
91
+ // Detect readable() stores
92
+ if (
93
+ node.init.type === 'CallExpression' &&
94
+ node.init.callee.name === 'readable'
95
+ ) {
96
+ const storeName = node.id.name;
97
+ reactiveStores.set(storeName, 'readable');
98
+ }
99
+ }
100
+ },
101
+
102
+ // Detect store mutations: $store = value, store.set(value), store.update(fn)
103
+ AssignmentExpression(path) {
104
+ const { node } = path;
105
+
106
+ // Detect direct assignments: count = 5
107
+ if (node.left.type === 'Identifier') {
108
+ const varName = node.left.name;
109
+
110
+ // Only emit if variable is used in template
111
+ if (templateStateVars.has(varName)) {
112
+ const location = node.loc;
113
+ const line = scriptBlock.startLine + (location ? location.start.line - 1 : 0);
114
+
115
+ expectations.push({
116
+ type: 'state',
117
+ expectedTarget: varName,
118
+ context: 'assignment',
119
+ sourceRef: {
120
+ file: filePath,
121
+ line,
122
+ snippet: scriptBlock.content.substring(
123
+ node.start - (ast.program.body[0]?.start || 0),
124
+ node.end - (ast.program.body[0]?.start || 0)
125
+ ),
126
+ },
127
+ proof: 'PROVEN_EXPECTATION',
128
+ metadata: {
129
+ templateUsage: Array.from(templateStateVars).filter(v => v === varName).length,
130
+ stateType: 'variable',
131
+ },
132
+ });
133
+ }
134
+ }
135
+
136
+ // Detect store assignments: $store = value
137
+ if (
138
+ node.left.type === 'Identifier' &&
139
+ node.left.name.startsWith('$') &&
140
+ reactiveStores.has(node.left.name.substring(1))
141
+ ) {
142
+ const storeName = node.left.name.substring(1);
143
+ const location = node.loc;
144
+ const line = scriptBlock.startLine + (location ? location.start.line - 1 : 0);
145
+
146
+ expectations.push({
147
+ type: 'state',
148
+ expectedTarget: storeName,
149
+ context: 'store-assignment',
150
+ sourceRef: {
151
+ file: filePath,
152
+ line,
153
+ snippet: scriptBlock.content.substring(
154
+ node.start - (ast.program.body[0]?.start || 0),
155
+ node.end - (ast.program.body[0]?.start || 0)
156
+ ),
157
+ },
158
+ proof: 'PROVEN_EXPECTATION',
159
+ metadata: {
160
+ stateType: 'store',
161
+ storeType: reactiveStores.get(storeName),
162
+ },
163
+ });
164
+ }
165
+ },
166
+
167
+ // Detect store.set() calls
168
+ CallExpression(path) {
169
+ const { node } = path;
170
+
171
+ if (
172
+ node.callee.type === 'MemberExpression' &&
173
+ node.callee.property.name === 'set' &&
174
+ node.callee.object.type === 'Identifier' &&
175
+ reactiveStores.has(node.callee.object.name)
176
+ ) {
177
+ const storeName = node.callee.object.name;
178
+ const location = node.loc;
179
+ const line = scriptBlock.startLine + (location ? location.start.line - 1 : 0);
180
+
181
+ expectations.push({
182
+ type: 'state',
183
+ expectedTarget: storeName,
184
+ context: 'store-set',
185
+ sourceRef: {
186
+ file: filePath,
187
+ line,
188
+ snippet: scriptBlock.content.substring(
189
+ node.start - (ast.program.body[0]?.start || 0),
190
+ node.end - (ast.program.body[0]?.start || 0)
191
+ ),
192
+ },
193
+ proof: 'PROVEN_EXPECTATION',
194
+ metadata: {
195
+ stateType: 'store',
196
+ storeType: reactiveStores.get(storeName),
197
+ },
198
+ });
199
+ }
200
+
201
+ // Detect store.update() calls
202
+ if (
203
+ node.callee.type === 'MemberExpression' &&
204
+ node.callee.property.name === 'update' &&
205
+ node.callee.object.type === 'Identifier' &&
206
+ reactiveStores.has(node.callee.object.name)
207
+ ) {
208
+ const storeName = node.callee.object.name;
209
+ const location = node.loc;
210
+ const line = scriptBlock.startLine + (location ? location.start.line - 1 : 0);
211
+
212
+ expectations.push({
213
+ type: 'state',
214
+ expectedTarget: storeName,
215
+ context: 'store-update',
216
+ sourceRef: {
217
+ file: filePath,
218
+ line,
219
+ snippet: scriptBlock.content.substring(
220
+ node.start - (ast.program.body[0]?.start || 0),
221
+ node.end - (ast.program.body[0]?.start || 0)
222
+ ),
223
+ },
224
+ proof: 'PROVEN_EXPECTATION',
225
+ metadata: {
226
+ stateType: 'store',
227
+ storeType: reactiveStores.get(storeName),
228
+ },
229
+ });
230
+ }
231
+ },
232
+ });
233
+ } catch (parseError) {
234
+ // Skip if parsing fails
235
+ }
236
+ }
237
+ } catch (error) {
238
+ // Skip if extraction fails
239
+ }
240
+
241
+ return expectations;
242
+ }
243
+
@@ -0,0 +1,177 @@
1
+ /**
2
+ * PHASE 20 — Vue Navigation Promise Detection
3
+ *
4
+ * Detects Vue Router navigation promises:
5
+ * - router.push('/path'), router.replace('/path')
6
+ * - router.push({ name: 'X', params: { id: 1 }}) -> mark as dynamic/ambiguous
7
+ */
8
+
9
+ import { parse } from '@babel/parser';
10
+ import _traverse from '@babel/traverse';
11
+
12
+ const traverse = _traverse.default || _traverse;
13
+
14
+ /**
15
+ * PHASE 20: Detect Vue navigation promises
16
+ *
17
+ * @param {string} scriptContent - Script block content
18
+ * @param {string} filePath - File path
19
+ * @param {string} relPath - Relative path
20
+ * @param {Object} scriptBlock - Script block metadata
21
+ * @param {Object} templateBindings - Template bindings (optional)
22
+ * @returns {Array} Navigation promises
23
+ */
24
+ export function detectVueNavigationPromises(scriptContent, filePath, relPath, scriptBlock, templateBindings) {
25
+ const promises = [];
26
+
27
+ try {
28
+ const ast = parse(scriptContent, {
29
+ sourceType: 'module',
30
+ plugins: [
31
+ 'typescript',
32
+ 'classProperties',
33
+ 'optionalChaining',
34
+ 'nullishCoalescingOperator',
35
+ 'dynamicImport',
36
+ 'topLevelAwait',
37
+ 'objectRestSpread',
38
+ ],
39
+ errorRecovery: true,
40
+ });
41
+
42
+ const lines = scriptContent.split('\n');
43
+
44
+ traverse(ast, {
45
+ CallExpression(path) {
46
+ const node = path.node;
47
+ const callee = node.callee;
48
+
49
+ // Detect router.push() and router.replace()
50
+ if (callee.type === 'MemberExpression') {
51
+ const object = callee.object;
52
+ const property = callee.property;
53
+
54
+ if (property.name === 'push' || property.name === 'replace') {
55
+ // Check if object is 'router' or 'this.$router' or 'useRouter()'
56
+ const isRouter =
57
+ (object.type === 'Identifier' && object.name === 'router') ||
58
+ (object.type === 'MemberExpression' &&
59
+ object.object.type === 'ThisExpression' &&
60
+ object.property.name === '$router') ||
61
+ (object.type === 'CallExpression' &&
62
+ callee.type === 'Identifier' &&
63
+ callee.name === 'useRouter');
64
+
65
+ if (isRouter && node.arguments.length > 0) {
66
+ const arg = node.arguments[0];
67
+ let targetPath = null;
68
+ let isDynamic = false;
69
+
70
+ // String literal: router.push('/path')
71
+ if (arg.type === 'StringLiteral') {
72
+ targetPath = arg.value;
73
+ }
74
+ // Object literal: router.push({ name: 'X', params: {} })
75
+ else if (arg.type === 'ObjectExpression') {
76
+ const nameProp = arg.properties.find(p =>
77
+ p.key && p.key.name === 'name'
78
+ );
79
+ const pathProp = arg.properties.find(p =>
80
+ p.key && p.key.name === 'path'
81
+ );
82
+
83
+ if (pathProp && pathProp.value && pathProp.value.type === 'StringLiteral') {
84
+ targetPath = pathProp.value.value;
85
+ } else if (nameProp) {
86
+ // Named route - mark as dynamic/ambiguous
87
+ isDynamic = true;
88
+ targetPath = '<named-route>';
89
+ } else {
90
+ isDynamic = true;
91
+ targetPath = '<dynamic>';
92
+ }
93
+ }
94
+ // Template literal or other dynamic
95
+ else {
96
+ isDynamic = true;
97
+ targetPath = '<dynamic>';
98
+ }
99
+
100
+ if (targetPath) {
101
+ const loc = node.loc;
102
+ const line = loc ? loc.start.line : 1;
103
+ const column = loc ? loc.start.column : 0;
104
+
105
+ // Extract AST source
106
+ const astSource = lines.slice(line - 1, loc ? loc.end.line : line)
107
+ .join('\n')
108
+ .substring(0, 200);
109
+
110
+ // Build context chain
111
+ const context = buildContext(path);
112
+
113
+ promises.push({
114
+ type: 'navigation',
115
+ promise: {
116
+ kind: 'navigate',
117
+ value: targetPath,
118
+ isDynamic,
119
+ },
120
+ source: {
121
+ file: relPath,
122
+ line,
123
+ column,
124
+ context,
125
+ astSource,
126
+ },
127
+ confidence: isDynamic ? 0.7 : 1.0,
128
+ });
129
+ }
130
+ }
131
+ }
132
+ }
133
+ },
134
+ });
135
+ } catch (error) {
136
+ // Parse error - skip
137
+ }
138
+
139
+ return promises;
140
+ }
141
+
142
+ /**
143
+ * Build context chain from AST path
144
+ */
145
+ function buildContext(path) {
146
+ const context = [];
147
+ let current = path;
148
+
149
+ while (current) {
150
+ if (current.isFunctionDeclaration()) {
151
+ context.push({
152
+ type: 'function',
153
+ name: current.node.id?.name || '<anonymous>',
154
+ });
155
+ } else if (current.isArrowFunctionExpression()) {
156
+ context.push({
157
+ type: 'arrow-function',
158
+ name: '<arrow>',
159
+ });
160
+ } else if (current.isMethodDefinition()) {
161
+ context.push({
162
+ type: 'method',
163
+ name: current.node.key?.name || '<method>',
164
+ });
165
+ } else if (current.isObjectProperty()) {
166
+ context.push({
167
+ type: 'property',
168
+ name: current.node.key?.name || '<property>',
169
+ });
170
+ }
171
+
172
+ current = current.parentPath;
173
+ }
174
+
175
+ return context.reverse().map(c => `${c.type}:${c.name}`).join(' > ');
176
+ }
177
+