@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,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
+
@@ -0,0 +1,162 @@
1
+ /**
2
+ * PHASE 20 — Vue SFC (Single File Component) Extractor
3
+ *
4
+ * Extracts <script>, <script setup>, and <template> content from .vue files.
5
+ * Deterministic and robust (no external runtime execution).
6
+ */
7
+
8
+ /**
9
+ * PHASE 20: Extract Vue SFC blocks
10
+ *
11
+ * @param {string} content - Full .vue file content
12
+ * @param {string} filePath - Path to the .vue file (for context)
13
+ * @returns {Object} { scriptBlocks: [{content, lang, startLine}], template: {content, startLine} }
14
+ */
15
+ export function extractVueSFC(content) {
16
+ const scriptBlocks = [];
17
+ let template = null;
18
+
19
+ // Extract <script> blocks (including <script setup>)
20
+ const scriptRegex = /<script(?:\s+setup)?(?:\s+lang=["']([^"']+)["'])?[^>]*>([\s\S]*?)<\/script>/gi;
21
+ let scriptMatch;
22
+ let lineOffset = 1;
23
+
24
+ while ((scriptMatch = scriptRegex.exec(content)) !== null) {
25
+ const isSetup = scriptMatch[0].includes('setup');
26
+ const lang = scriptMatch[1] || 'js';
27
+ const scriptContent = scriptMatch[2];
28
+
29
+ // Calculate start line
30
+ const beforeMatch = content.substring(0, scriptMatch.index);
31
+ const startLine = (beforeMatch.match(/\n/g) || []).length + 1;
32
+
33
+ scriptBlocks.push({
34
+ content: scriptContent.trim(),
35
+ lang: lang.toLowerCase(),
36
+ startLine,
37
+ isSetup,
38
+ });
39
+ }
40
+
41
+ // Extract <template> block
42
+ const templateRegex = /<template[^>]*>([\s\S]*?)<\/template>/i;
43
+ const templateMatch = content.match(templateRegex);
44
+
45
+ if (templateMatch) {
46
+ const beforeTemplate = content.substring(0, templateMatch.index);
47
+ const templateStartLine = (beforeTemplate.match(/\n/g) || []).length + 1;
48
+
49
+ template = {
50
+ content: templateMatch[1].trim(),
51
+ startLine: templateStartLine,
52
+ };
53
+ }
54
+
55
+ return {
56
+ scriptBlocks,
57
+ template,
58
+ };
59
+ }
60
+
61
+ /**
62
+ * PHASE 20: Extract template bindings and references
63
+ *
64
+ * @param {string} templateContent - Template content
65
+ * @returns {Object} { bindings: string[], routerLinks: Array, eventHandlers: Array }
66
+ */
67
+ export function extractTemplateBindings(templateContent) {
68
+ const bindings = [];
69
+ const routerLinks = [];
70
+ const eventHandlers = [];
71
+
72
+ // Extract variable bindings: {{ var }}, :prop="var", v-if="var", etc.
73
+ const bindingPatterns = [
74
+ /\{\{\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\}\}/g, // {{ var }}
75
+ /:([a-zA-Z-]+)=["']([^"']+)["']/g, // :prop="value"
76
+ /v-if=["']([^"']+)["']/g, // v-if="condition"
77
+ /v-show=["']([^"']+)["']/g, // v-show="condition"
78
+ /v-model=["']([^"']+)["']/g, // v-model="value"
79
+ ];
80
+
81
+ for (const pattern of bindingPatterns) {
82
+ let match;
83
+ while ((match = pattern.exec(templateContent)) !== null) {
84
+ const binding = match[1] || match[2];
85
+ if (binding && !bindings.includes(binding)) {
86
+ bindings.push(binding);
87
+ }
88
+ }
89
+ }
90
+
91
+ // Extract <router-link> usage
92
+ const routerLinkRegex = /<router-link[^>]*\s+to=["']([^"']+)["'][^>]*>/gi;
93
+ let routerLinkMatch;
94
+ while ((routerLinkMatch = routerLinkRegex.exec(templateContent)) !== null) {
95
+ routerLinks.push({
96
+ to: routerLinkMatch[1],
97
+ fullMatch: routerLinkMatch[0],
98
+ });
99
+ }
100
+
101
+ // Extract event handlers: @click="handler", @submit.prevent="handler"
102
+ const eventHandlerRegex = /@([a-z]+)(?:\.([a-z]+)*)?=["']([^"']+)["']/gi;
103
+ let eventMatch;
104
+ while ((eventMatch = eventHandlerRegex.exec(templateContent)) !== null) {
105
+ eventHandlers.push({
106
+ event: eventMatch[1],
107
+ modifiers: eventMatch[2] ? eventMatch[2].split('.') : [],
108
+ handler: eventMatch[3],
109
+ });
110
+ }
111
+
112
+ return {
113
+ bindings,
114
+ routerLinks,
115
+ eventHandlers,
116
+ };
117
+ }
118
+
119
+ /**
120
+ * PHASE 20: Map template handlers to script functions
121
+ *
122
+ * @param {Object} templateBindings - Result from extractTemplateBindings
123
+ * @param {Array} scriptBlocks - Script blocks from extractVueSFC
124
+ * @returns {Map} handlerName -> { scriptBlock, functionInfo }
125
+ */
126
+ export function mapTemplateHandlersToScript(templateBindings, scriptBlocks) {
127
+ const handlerMap = new Map();
128
+
129
+ for (const handler of templateBindings.eventHandlers) {
130
+ const handlerName = handler.handler;
131
+
132
+ // Find handler in script blocks
133
+ for (const scriptBlock of scriptBlocks) {
134
+ const scriptContent = scriptBlock.content;
135
+
136
+ // Look for function declarations: function handlerName() {}
137
+ const functionRegex = new RegExp(`(?:function|const|let|var)\\s+${handlerName}\\s*[=(]`, 'g');
138
+ if (functionRegex.test(scriptContent)) {
139
+ handlerMap.set(handlerName, {
140
+ scriptBlock,
141
+ handler,
142
+ type: 'function',
143
+ });
144
+ break;
145
+ }
146
+
147
+ // Look for method definitions: methods: { handlerName() {} }
148
+ const methodRegex = new RegExp(`(?:methods|setup)\\s*:\\s*\\{[^}]*${handlerName}\\s*[:(]`, 's');
149
+ if (methodRegex.test(scriptContent)) {
150
+ handlerMap.set(handlerName, {
151
+ scriptBlock,
152
+ handler,
153
+ type: 'method',
154
+ });
155
+ break;
156
+ }
157
+ }
158
+ }
159
+
160
+ return handlerMap;
161
+ }
162
+