@veraxhq/verax 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -18
- package/bin/verax.js +7 -0
- package/package.json +15 -5
- package/src/cli/commands/baseline.js +104 -0
- package/src/cli/commands/default.js +323 -111
- package/src/cli/commands/doctor.js +36 -4
- package/src/cli/commands/ga.js +243 -0
- package/src/cli/commands/gates.js +95 -0
- package/src/cli/commands/inspect.js +131 -2
- package/src/cli/commands/release-check.js +213 -0
- package/src/cli/commands/run.js +498 -103
- package/src/cli/commands/security-check.js +211 -0
- package/src/cli/commands/truth.js +114 -0
- package/src/cli/entry.js +305 -68
- package/src/cli/util/angular-component-extractor.js +179 -0
- package/src/cli/util/angular-navigation-detector.js +141 -0
- package/src/cli/util/angular-network-detector.js +161 -0
- package/src/cli/util/angular-state-detector.js +162 -0
- package/src/cli/util/ast-interactive-detector.js +546 -0
- package/src/cli/util/ast-network-detector.js +603 -0
- package/src/cli/util/ast-usestate-detector.js +602 -0
- package/src/cli/util/bootstrap-guard.js +86 -0
- package/src/cli/util/detection-engine.js +4 -3
- package/src/cli/util/determinism-runner.js +123 -0
- package/src/cli/util/determinism-writer.js +129 -0
- package/src/cli/util/env-url.js +4 -0
- package/src/cli/util/events.js +76 -0
- package/src/cli/util/expectation-extractor.js +380 -74
- package/src/cli/util/findings-writer.js +126 -15
- package/src/cli/util/learn-writer.js +3 -1
- package/src/cli/util/observation-engine.js +69 -23
- package/src/cli/util/observe-writer.js +3 -1
- package/src/cli/util/paths.js +6 -14
- package/src/cli/util/project-discovery.js +23 -0
- package/src/cli/util/project-writer.js +3 -1
- package/src/cli/util/redact.js +2 -2
- package/src/cli/util/run-resolver.js +64 -0
- package/src/cli/util/runtime-budget.js +147 -0
- package/src/cli/util/source-requirement.js +55 -0
- package/src/cli/util/summary-writer.js +13 -1
- package/src/cli/util/svelte-navigation-detector.js +163 -0
- package/src/cli/util/svelte-network-detector.js +80 -0
- package/src/cli/util/svelte-sfc-extractor.js +147 -0
- package/src/cli/util/svelte-state-detector.js +243 -0
- package/src/cli/util/vue-navigation-detector.js +177 -0
- package/src/cli/util/vue-sfc-extractor.js +162 -0
- package/src/cli/util/vue-state-detector.js +215 -0
- package/src/types/global.d.ts +28 -0
- package/src/types/ts-ast.d.ts +24 -0
- package/src/verax/cli/doctor.js +2 -2
- package/src/verax/cli/finding-explainer.js +56 -3
- package/src/verax/cli/init.js +1 -1
- package/src/verax/cli/url-safety.js +12 -2
- package/src/verax/cli/wizard.js +13 -2
- package/src/verax/core/artifacts/registry.js +154 -0
- package/src/verax/core/artifacts/verifier.js +980 -0
- package/src/verax/core/baseline/baseline.enforcer.js +137 -0
- package/src/verax/core/baseline/baseline.snapshot.js +231 -0
- package/src/verax/core/budget-engine.js +1 -1
- package/src/verax/core/capabilities/gates.js +499 -0
- package/src/verax/core/capabilities/registry.js +475 -0
- package/src/verax/core/confidence/confidence-compute.js +137 -0
- package/src/verax/core/confidence/confidence-invariants.js +234 -0
- package/src/verax/core/confidence/confidence-report-writer.js +112 -0
- package/src/verax/core/confidence/confidence-weights.js +44 -0
- package/src/verax/core/confidence/confidence.defaults.js +65 -0
- package/src/verax/core/confidence/confidence.loader.js +79 -0
- package/src/verax/core/confidence/confidence.schema.js +94 -0
- package/src/verax/core/confidence-engine-refactor.js +484 -0
- package/src/verax/core/confidence-engine.js +486 -0
- package/src/verax/core/confidence-engine.js.backup +471 -0
- package/src/verax/core/contracts/index.js +29 -0
- package/src/verax/core/contracts/types.js +185 -0
- package/src/verax/core/contracts/validators.js +381 -0
- package/src/verax/core/decision-snapshot.js +31 -4
- package/src/verax/core/decisions/decision.trace.js +276 -0
- package/src/verax/core/determinism/contract-writer.js +89 -0
- package/src/verax/core/determinism/contract.js +139 -0
- package/src/verax/core/determinism/diff.js +364 -0
- package/src/verax/core/determinism/engine.js +221 -0
- package/src/verax/core/determinism/finding-identity.js +148 -0
- package/src/verax/core/determinism/normalize.js +438 -0
- package/src/verax/core/determinism/report-writer.js +92 -0
- package/src/verax/core/determinism/run-fingerprint.js +118 -0
- package/src/verax/core/determinism-model.js +35 -6
- package/src/verax/core/dynamic-route-intelligence.js +528 -0
- package/src/verax/core/evidence/evidence-capture-service.js +307 -0
- package/src/verax/core/evidence/evidence-intent-ledger.js +165 -0
- package/src/verax/core/evidence-builder.js +487 -0
- package/src/verax/core/execution-mode-context.js +77 -0
- package/src/verax/core/execution-mode-detector.js +190 -0
- package/src/verax/core/failures/exit-codes.js +86 -0
- package/src/verax/core/failures/failure-summary.js +76 -0
- package/src/verax/core/failures/failure.factory.js +225 -0
- package/src/verax/core/failures/failure.ledger.js +132 -0
- package/src/verax/core/failures/failure.types.js +196 -0
- package/src/verax/core/failures/index.js +10 -0
- package/src/verax/core/ga/ga-report-writer.js +43 -0
- package/src/verax/core/ga/ga.artifact.js +49 -0
- package/src/verax/core/ga/ga.contract.js +434 -0
- package/src/verax/core/ga/ga.enforcer.js +86 -0
- package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
- package/src/verax/core/guardrails/policy.defaults.js +210 -0
- package/src/verax/core/guardrails/policy.loader.js +83 -0
- package/src/verax/core/guardrails/policy.schema.js +110 -0
- package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
- package/src/verax/core/guardrails-engine.js +505 -0
- package/src/verax/core/incremental-store.js +15 -7
- package/src/verax/core/observe/run-timeline.js +316 -0
- package/src/verax/core/perf/perf.contract.js +186 -0
- package/src/verax/core/perf/perf.display.js +65 -0
- package/src/verax/core/perf/perf.enforcer.js +91 -0
- package/src/verax/core/perf/perf.monitor.js +209 -0
- package/src/verax/core/perf/perf.report.js +198 -0
- package/src/verax/core/pipeline-tracker.js +238 -0
- package/src/verax/core/product-definition.js +127 -0
- package/src/verax/core/release/provenance.builder.js +271 -0
- package/src/verax/core/release/release-report-writer.js +40 -0
- package/src/verax/core/release/release.enforcer.js +159 -0
- package/src/verax/core/release/reproducibility.check.js +221 -0
- package/src/verax/core/release/sbom.builder.js +283 -0
- package/src/verax/core/replay-validator.js +4 -4
- package/src/verax/core/replay.js +1 -1
- package/src/verax/core/report/cross-index.js +192 -0
- package/src/verax/core/report/human-summary.js +222 -0
- package/src/verax/core/route-intelligence.js +419 -0
- package/src/verax/core/security/secrets.scan.js +326 -0
- package/src/verax/core/security/security-report.js +50 -0
- package/src/verax/core/security/security.enforcer.js +124 -0
- package/src/verax/core/security/supplychain.defaults.json +38 -0
- package/src/verax/core/security/supplychain.policy.js +326 -0
- package/src/verax/core/security/vuln.scan.js +265 -0
- package/src/verax/core/silence-impact.js +1 -1
- package/src/verax/core/silence-model.js +9 -7
- package/src/verax/core/truth/truth.certificate.js +250 -0
- package/src/verax/core/ui-feedback-intelligence.js +515 -0
- package/src/verax/detect/comparison.js +8 -3
- package/src/verax/detect/confidence-engine.js +645 -57
- package/src/verax/detect/confidence-helper.js +33 -0
- package/src/verax/detect/detection-engine.js +19 -2
- package/src/verax/detect/dynamic-route-findings.js +335 -0
- package/src/verax/detect/evidence-index.js +15 -65
- package/src/verax/detect/expectation-chain-detector.js +417 -0
- package/src/verax/detect/expectation-model.js +56 -3
- package/src/verax/detect/explanation-helpers.js +1 -1
- package/src/verax/detect/finding-detector.js +2 -2
- package/src/verax/detect/findings-writer.js +149 -20
- package/src/verax/detect/flow-detector.js +4 -4
- package/src/verax/detect/index.js +265 -15
- package/src/verax/detect/interactive-findings.js +3 -4
- package/src/verax/detect/journey-stall-detector.js +558 -0
- package/src/verax/detect/route-findings.js +218 -0
- package/src/verax/detect/signal-mapper.js +2 -2
- package/src/verax/detect/skip-classifier.js +4 -4
- package/src/verax/detect/ui-feedback-findings.js +207 -0
- package/src/verax/detect/verdict-engine.js +61 -9
- package/src/verax/detect/view-switch-correlator.js +242 -0
- package/src/verax/flow/flow-engine.js +3 -2
- package/src/verax/flow/flow-spec.js +1 -2
- package/src/verax/index.js +413 -33
- package/src/verax/intel/effect-detector.js +1 -1
- package/src/verax/intel/index.js +2 -2
- package/src/verax/intel/route-extractor.js +3 -3
- package/src/verax/intel/vue-navigation-extractor.js +81 -18
- package/src/verax/intel/vue-router-extractor.js +4 -2
- package/src/verax/learn/action-contract-extractor.js +684 -66
- package/src/verax/learn/ast-contract-extractor.js +53 -1
- package/src/verax/learn/index.js +36 -2
- package/src/verax/learn/manifest-writer.js +28 -14
- package/src/verax/learn/route-extractor.js +1 -1
- package/src/verax/learn/route-validator.js +12 -8
- package/src/verax/learn/state-extractor.js +1 -1
- package/src/verax/learn/static-extractor-navigation.js +1 -1
- package/src/verax/learn/static-extractor-validation.js +2 -2
- package/src/verax/learn/static-extractor.js +8 -7
- package/src/verax/learn/ts-contract-resolver.js +14 -12
- package/src/verax/observe/browser.js +22 -3
- package/src/verax/observe/console-sensor.js +2 -2
- package/src/verax/observe/expectation-executor.js +2 -1
- package/src/verax/observe/focus-sensor.js +1 -1
- package/src/verax/observe/human-driver.js +29 -10
- package/src/verax/observe/index.js +92 -844
- package/src/verax/observe/interaction-discovery.js +27 -15
- package/src/verax/observe/interaction-runner.js +31 -14
- package/src/verax/observe/loading-sensor.js +6 -0
- package/src/verax/observe/navigation-sensor.js +1 -1
- package/src/verax/observe/observe-context.js +205 -0
- package/src/verax/observe/observe-helpers.js +191 -0
- package/src/verax/observe/observe-runner.js +226 -0
- package/src/verax/observe/observers/budget-observer.js +185 -0
- package/src/verax/observe/observers/console-observer.js +102 -0
- package/src/verax/observe/observers/coverage-observer.js +107 -0
- package/src/verax/observe/observers/interaction-observer.js +471 -0
- package/src/verax/observe/observers/navigation-observer.js +132 -0
- package/src/verax/observe/observers/network-observer.js +87 -0
- package/src/verax/observe/observers/safety-observer.js +82 -0
- package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
- package/src/verax/observe/settle.js +1 -0
- package/src/verax/observe/state-sensor.js +8 -4
- package/src/verax/observe/state-ui-sensor.js +7 -1
- package/src/verax/observe/traces-writer.js +27 -16
- package/src/verax/observe/ui-feedback-detector.js +742 -0
- package/src/verax/observe/ui-signal-sensor.js +155 -2
- package/src/verax/scan-summary-writer.js +46 -9
- package/src/verax/shared/artifact-manager.js +9 -6
- package/src/verax/shared/budget-profiles.js +2 -2
- package/src/verax/shared/caching.js +1 -1
- package/src/verax/shared/config-loader.js +1 -2
- package/src/verax/shared/css-spinner-rules.js +204 -0
- package/src/verax/shared/dynamic-route-utils.js +12 -6
- package/src/verax/shared/retry-policy.js +1 -6
- package/src/verax/shared/root-artifacts.js +1 -1
- package/src/verax/shared/view-switch-rules.js +208 -0
- package/src/verax/shared/zip-artifacts.js +1 -0
- package/src/verax/validate/context-validator.js +1 -1
- package/src/verax/observe/index.js.backup +0 -1
- package/src/verax/validate/context-validator.js.bak +0 -0
|
@@ -1,13 +1,27 @@
|
|
|
1
1
|
import { readdirSync, readFileSync, statSync } from 'fs';
|
|
2
2
|
import { join, relative, resolve } from 'path';
|
|
3
3
|
import { expIdFromHash, compareExpectations } from './idgen.js';
|
|
4
|
+
import { detectNetworkCallsAST } from './ast-network-detector.js';
|
|
5
|
+
import { detectUseStatePromises } from './ast-usestate-detector.js';
|
|
6
|
+
import { detectInteractiveElementsAST } from './ast-interactive-detector.js';
|
|
7
|
+
import { extractVueSFC, extractTemplateBindings, mapTemplateHandlersToScript } from './vue-sfc-extractor.js';
|
|
8
|
+
import { detectVueNavigationPromises } from './vue-navigation-detector.js';
|
|
9
|
+
import { detectVueStatePromises } from './vue-state-detector.js';
|
|
10
|
+
import { extractSvelteSFC, extractTemplateBindings as extractSvelteTemplateBindings, mapTemplateHandlersToScript as mapSvelteTemplateHandlersToScript } from './svelte-sfc-extractor.js';
|
|
11
|
+
import { detectSvelteNavigation } from './svelte-navigation-detector.js';
|
|
12
|
+
import { detectSvelteNetwork } from './svelte-network-detector.js';
|
|
13
|
+
import { detectSvelteState } from './svelte-state-detector.js';
|
|
14
|
+
import { extractAngularComponent, extractTemplateBindings as extractAngularTemplateBindings, mapTemplateHandlersToClass } from './angular-component-extractor.js';
|
|
15
|
+
import { detectAngularNavigation } from './angular-navigation-detector.js';
|
|
16
|
+
import { detectAngularNetwork } from './angular-network-detector.js';
|
|
17
|
+
import { detectAngularState } from './angular-state-detector.js';
|
|
4
18
|
|
|
5
19
|
/**
|
|
6
20
|
* Static Expectation Extractor
|
|
7
21
|
* Extracts explicit, static expectations from source files
|
|
8
22
|
*/
|
|
9
23
|
|
|
10
|
-
export async function extractExpectations(projectProfile,
|
|
24
|
+
export async function extractExpectations(projectProfile, _srcPath) {
|
|
11
25
|
const expectations = [];
|
|
12
26
|
const skipped = {
|
|
13
27
|
dynamic: 0,
|
|
@@ -68,6 +82,16 @@ function getScanPaths(projectProfile, sourceRoot) {
|
|
|
68
82
|
return [sourceRoot];
|
|
69
83
|
}
|
|
70
84
|
|
|
85
|
+
// Unknown framework or no framework detected - check if it's a static HTML project
|
|
86
|
+
// (This handles cases where framework detection failed but HTML files exist)
|
|
87
|
+
if (framework === 'unknown') {
|
|
88
|
+
const htmlFiles = readdirSync(sourceRoot, { withFileTypes: true })
|
|
89
|
+
.filter(e => e.isFile() && e.name.endsWith('.html'));
|
|
90
|
+
if (htmlFiles.length > 0) {
|
|
91
|
+
return [sourceRoot];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
71
95
|
// Unknown framework - scan src if it exists
|
|
72
96
|
const srcPath = resolve(sourceRoot, 'src');
|
|
73
97
|
try {
|
|
@@ -138,8 +162,8 @@ function shouldSkipDirectory(name) {
|
|
|
138
162
|
* Check if file should be scanned
|
|
139
163
|
*/
|
|
140
164
|
function shouldScanFile(name) {
|
|
141
|
-
const extensions = ['.js', '.jsx', '.ts', '.tsx', '.html', '.mjs'];
|
|
142
|
-
return extensions.some(ext => name.endsWith(ext));
|
|
165
|
+
const extensions = ['.js', '.jsx', '.ts', '.tsx', '.html', '.mjs', '.vue', '.svelte'];
|
|
166
|
+
return extensions.some(ext => name.endsWith(ext)) || (name.endsWith('.component.ts') || name.endsWith('.service.ts'));
|
|
143
167
|
}
|
|
144
168
|
|
|
145
169
|
/**
|
|
@@ -155,6 +179,15 @@ function scanFile(filePath, sourceRoot, skipped) {
|
|
|
155
179
|
if (filePath.endsWith('.html')) {
|
|
156
180
|
const htmlExpectations = extractHtmlExpectations(content, filePath, relPath);
|
|
157
181
|
expectations.push(...htmlExpectations);
|
|
182
|
+
} else if (filePath.endsWith('.vue')) {
|
|
183
|
+
const vueExpectations = extractVueExpectations(content, filePath, relPath, skipped);
|
|
184
|
+
expectations.push(...vueExpectations);
|
|
185
|
+
} else if (filePath.endsWith('.svelte')) {
|
|
186
|
+
const svelteExpectations = extractSvelteExpectations(content, filePath, relPath, skipped);
|
|
187
|
+
expectations.push(...svelteExpectations);
|
|
188
|
+
} else if (filePath.endsWith('.component.ts') || (filePath.endsWith('.ts') && content.includes('@Component'))) {
|
|
189
|
+
const angularExpectations = extractAngularExpectations(content, filePath, relPath, skipped);
|
|
190
|
+
expectations.push(...angularExpectations);
|
|
158
191
|
} else {
|
|
159
192
|
const jsExpectations = extractJsExpectations(content, filePath, relPath, skipped);
|
|
160
193
|
expectations.push(...jsExpectations);
|
|
@@ -168,9 +201,11 @@ function scanFile(filePath, sourceRoot, skipped) {
|
|
|
168
201
|
|
|
169
202
|
/**
|
|
170
203
|
* Extract expectations from HTML files
|
|
204
|
+
* PHASE 9: Enhanced to extract network calls from <script> tags using AST
|
|
171
205
|
*/
|
|
172
206
|
function extractHtmlExpectations(content, filePath, relPath) {
|
|
173
207
|
const expectations = [];
|
|
208
|
+
const skipped = { dynamic: 0, computed: 0, external: 0, parseError: 0, other: 0 };
|
|
174
209
|
|
|
175
210
|
// Extract <a href="/path"> links
|
|
176
211
|
const hrefRegex = /<a\s+[^>]*href=["']([^"']+)["']/gi;
|
|
@@ -198,6 +233,72 @@ function extractHtmlExpectations(content, filePath, relPath) {
|
|
|
198
233
|
}
|
|
199
234
|
}
|
|
200
235
|
|
|
236
|
+
// PHASE 9: Extract JavaScript from <script> tags and detect network calls
|
|
237
|
+
const scriptRegex = /<script[^>]*>([\s\S]*?)<\/script>/gi;
|
|
238
|
+
let scriptMatch;
|
|
239
|
+
let scriptIndex = 0;
|
|
240
|
+
|
|
241
|
+
while ((scriptMatch = scriptRegex.exec(content)) !== null) {
|
|
242
|
+
const scriptContent = scriptMatch[1].trim();
|
|
243
|
+
const scriptStartIndex = scriptMatch.index;
|
|
244
|
+
const scriptStartLine = content.substring(0, scriptStartIndex).split('\n').length;
|
|
245
|
+
|
|
246
|
+
// Skip empty scripts and external scripts
|
|
247
|
+
if (!scriptContent || scriptMatch[0].includes('src=')) {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Use AST-based network detection on script content
|
|
252
|
+
try {
|
|
253
|
+
const networkCalls = detectNetworkCallsAST(scriptContent, filePath, relPath);
|
|
254
|
+
|
|
255
|
+
for (const call of networkCalls) {
|
|
256
|
+
const url = call.url;
|
|
257
|
+
|
|
258
|
+
// Skip dynamic URLs but count them
|
|
259
|
+
if (url === '<dynamic>') {
|
|
260
|
+
skipped.dynamic++;
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Only extract absolute URLs (http/https) or relative API paths
|
|
265
|
+
if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('/api/')) {
|
|
266
|
+
// Calculate correct line number in HTML file
|
|
267
|
+
const callLineInScript = call.location.line || 1;
|
|
268
|
+
const htmlLineNumber = scriptStartLine + callLineInScript - 1;
|
|
269
|
+
|
|
270
|
+
expectations.push({
|
|
271
|
+
type: 'network',
|
|
272
|
+
promise: {
|
|
273
|
+
kind: 'request',
|
|
274
|
+
value: url,
|
|
275
|
+
method: call.method,
|
|
276
|
+
},
|
|
277
|
+
source: {
|
|
278
|
+
file: relPath,
|
|
279
|
+
line: htmlLineNumber,
|
|
280
|
+
column: call.location.column || 0,
|
|
281
|
+
context: call.context,
|
|
282
|
+
astSource: call.astSource || null,
|
|
283
|
+
},
|
|
284
|
+
confidence: call.isUIBound ? 1.0 : 0.9,
|
|
285
|
+
metadata: {
|
|
286
|
+
networkKind: call.kind,
|
|
287
|
+
isUIBound: call.isUIBound || false,
|
|
288
|
+
astSource: call.astSource || null,
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
} else {
|
|
292
|
+
skipped.external++;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
} catch (error) {
|
|
296
|
+
skipped.parseError++;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
scriptIndex++;
|
|
300
|
+
}
|
|
301
|
+
|
|
201
302
|
return expectations;
|
|
202
303
|
}
|
|
203
304
|
|
|
@@ -208,76 +309,178 @@ function extractJsExpectations(content, filePath, relPath, skipped) {
|
|
|
208
309
|
const expectations = [];
|
|
209
310
|
const lines = content.split('\n');
|
|
210
311
|
|
|
211
|
-
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
if (line.trim().startsWith('//') || line.trim().startsWith('*')) {
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Extract Next.js <Link href="/path">
|
|
220
|
-
const linkRegex = /<Link\s+[^>]*href=["']([^"']+)["']/g;
|
|
221
|
-
let match;
|
|
222
|
-
while ((match = linkRegex.exec(line)) !== null) {
|
|
223
|
-
const href = match[1];
|
|
312
|
+
// PHASE 9: AST-based network detection (handles nested contexts)
|
|
313
|
+
const networkCalls = detectNetworkCallsAST(content, filePath, relPath);
|
|
314
|
+
for (const call of networkCalls) {
|
|
315
|
+
const url = call.url;
|
|
224
316
|
|
|
225
|
-
// Skip dynamic
|
|
226
|
-
if (
|
|
317
|
+
// Skip dynamic URLs but count them
|
|
318
|
+
if (url === '<dynamic>') {
|
|
319
|
+
skipped.dynamic++;
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Only extract absolute URLs (http/https)
|
|
324
|
+
if (url.startsWith('http://') || url.startsWith('https://')) {
|
|
227
325
|
expectations.push({
|
|
228
|
-
type: '
|
|
326
|
+
type: 'network',
|
|
229
327
|
promise: {
|
|
230
|
-
kind: '
|
|
231
|
-
value:
|
|
328
|
+
kind: 'request',
|
|
329
|
+
value: url,
|
|
330
|
+
method: call.method,
|
|
232
331
|
},
|
|
233
332
|
source: {
|
|
234
333
|
file: relPath,
|
|
235
|
-
line:
|
|
236
|
-
column:
|
|
334
|
+
line: call.location.line,
|
|
335
|
+
column: call.location.column,
|
|
336
|
+
context: call.context,
|
|
337
|
+
// PHASE 9: Include AST source code for evidence
|
|
338
|
+
astSource: call.astSource || null,
|
|
339
|
+
},
|
|
340
|
+
confidence: call.isUIBound ? 1.0 : 0.9, // Higher confidence for UI-bound handlers
|
|
341
|
+
metadata: {
|
|
342
|
+
networkKind: call.kind,
|
|
343
|
+
isUIBound: call.isUIBound || false,
|
|
344
|
+
// PHASE 9: Include AST source in metadata for evidence generation
|
|
345
|
+
astSource: call.astSource || null,
|
|
237
346
|
},
|
|
238
|
-
confidence: 1.0,
|
|
239
347
|
});
|
|
240
348
|
} else {
|
|
241
|
-
|
|
349
|
+
// Relative or non-http URLs
|
|
350
|
+
skipped.external++;
|
|
242
351
|
}
|
|
243
352
|
}
|
|
353
|
+
|
|
354
|
+
// PHASE 10: AST-based useState detection (state-driven UI promises)
|
|
355
|
+
const statePromises = detectUseStatePromises(content, filePath, relPath);
|
|
356
|
+
for (const statePromise of statePromises) {
|
|
357
|
+
// PHASE 10: Extract context and AST source from first setter call
|
|
358
|
+
const firstSetterCall = statePromise.metadata.setterCalls?.[0];
|
|
359
|
+
const context = firstSetterCall?.context || `component:${statePromise.componentName}`;
|
|
360
|
+
const astSource = firstSetterCall?.astSource || null;
|
|
361
|
+
const isUIBound = firstSetterCall?.isUIBound || false;
|
|
244
362
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
363
|
+
expectations.push({
|
|
364
|
+
type: 'state',
|
|
365
|
+
promise: {
|
|
366
|
+
kind: 'ui_state_change',
|
|
367
|
+
value: `${statePromise.stateName} in ${statePromise.componentName}`,
|
|
368
|
+
stateName: statePromise.stateName,
|
|
369
|
+
setterName: statePromise.setterName,
|
|
370
|
+
},
|
|
371
|
+
source: {
|
|
372
|
+
file: relPath,
|
|
373
|
+
line: statePromise.location.line,
|
|
374
|
+
column: statePromise.location.column,
|
|
375
|
+
context: context, // PHASE 10: Enhanced context (handler/hook/function)
|
|
376
|
+
astSource: astSource, // PHASE 10: AST source for evidence
|
|
377
|
+
},
|
|
378
|
+
confidence: isUIBound ? 1.0 : 0.9, // PHASE 10: Higher confidence for UI-bound handlers
|
|
379
|
+
metadata: {
|
|
380
|
+
componentName: statePromise.componentName,
|
|
381
|
+
setterCallCount: statePromise.setterCallCount,
|
|
382
|
+
jsxUsageCount: statePromise.jsxUsageCount,
|
|
383
|
+
usageTypes: statePromise.usageTypes,
|
|
384
|
+
hasUpdaterFunction: statePromise.metadata.hasUpdaterFunction,
|
|
385
|
+
// PHASE 10: Include all setter calls with context and AST source
|
|
386
|
+
setterCalls: statePromise.metadata.setterCalls || [],
|
|
387
|
+
isUIBound: isUIBound,
|
|
388
|
+
astSource: astSource, // PHASE 10: AST source in metadata for evidence generation
|
|
389
|
+
},
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// PHASE 11: AST-based interactive element detection (elements without href)
|
|
394
|
+
const interactiveElements = detectInteractiveElementsAST(content, filePath, relPath);
|
|
395
|
+
for (const element of interactiveElements) {
|
|
396
|
+
// Only create expectations for elements with navigation promises
|
|
397
|
+
if (element.navigationPromise) {
|
|
398
|
+
const promise = element.navigationPromise;
|
|
249
399
|
|
|
250
|
-
|
|
400
|
+
// Skip dynamic targets
|
|
401
|
+
if (promise.target === '<dynamic>') {
|
|
402
|
+
skipped.dynamic++;
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
expectations.push({
|
|
407
|
+
type: 'navigation',
|
|
408
|
+
promise: {
|
|
409
|
+
kind: 'navigate',
|
|
410
|
+
value: promise.target,
|
|
411
|
+
method: promise.method || 'push',
|
|
412
|
+
},
|
|
413
|
+
source: {
|
|
414
|
+
file: relPath,
|
|
415
|
+
line: element.location.line,
|
|
416
|
+
column: element.location.column,
|
|
417
|
+
context: element.context,
|
|
418
|
+
astSource: element.astSource || promise.astSource || null,
|
|
419
|
+
},
|
|
420
|
+
confidence: element.isUIBound ? 1.0 : 0.9,
|
|
421
|
+
metadata: {
|
|
422
|
+
tagName: element.tagName,
|
|
423
|
+
role: element.role,
|
|
424
|
+
hasOnClick: element.hasOnClick,
|
|
425
|
+
hasOnSubmit: element.hasOnSubmit,
|
|
426
|
+
isRouterLink: element.isRouterLink,
|
|
427
|
+
navigationType: promise.type,
|
|
428
|
+
selectorHint: element.selectorHint,
|
|
429
|
+
isUIBound: element.isUIBound,
|
|
430
|
+
astSource: element.astSource || promise.astSource || null,
|
|
431
|
+
},
|
|
432
|
+
});
|
|
433
|
+
} else if (element.isRouterLink && (element.linkTo || element.linkHref)) {
|
|
434
|
+
// Router Link component with static href/to
|
|
435
|
+
const target = element.linkTo || element.linkHref;
|
|
436
|
+
|
|
437
|
+
if (target && target !== '<dynamic>') {
|
|
251
438
|
expectations.push({
|
|
252
439
|
type: 'navigation',
|
|
253
440
|
promise: {
|
|
254
441
|
kind: 'navigate',
|
|
255
|
-
value:
|
|
442
|
+
value: target,
|
|
256
443
|
},
|
|
257
444
|
source: {
|
|
258
445
|
file: relPath,
|
|
259
|
-
line:
|
|
260
|
-
column:
|
|
446
|
+
line: element.location.line,
|
|
447
|
+
column: element.location.column,
|
|
448
|
+
context: element.context,
|
|
449
|
+
astSource: element.astSource || null,
|
|
261
450
|
},
|
|
262
451
|
confidence: 1.0,
|
|
452
|
+
metadata: {
|
|
453
|
+
tagName: element.tagName,
|
|
454
|
+
isRouterLink: true,
|
|
455
|
+
selectorHint: element.selectorHint,
|
|
456
|
+
astSource: element.astSource || null,
|
|
457
|
+
},
|
|
263
458
|
});
|
|
264
|
-
} else {
|
|
265
|
-
skipped.dynamic++;
|
|
266
459
|
}
|
|
267
460
|
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
lines.forEach((line, lineIdx) => {
|
|
464
|
+
const lineNum = lineIdx + 1;
|
|
465
|
+
|
|
466
|
+
// Skip comments
|
|
467
|
+
if (line.trim().startsWith('//') || line.trim().startsWith('*')) {
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
268
470
|
|
|
269
|
-
// Extract
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
471
|
+
// Extract Next.js <Link href="/path">
|
|
472
|
+
const linkRegex = /<Link\s+[^>]*href=["']([^"']+)["']/g;
|
|
473
|
+
let match;
|
|
474
|
+
while ((match = linkRegex.exec(line)) !== null) {
|
|
475
|
+
const href = match[1];
|
|
273
476
|
|
|
274
|
-
//
|
|
275
|
-
if (
|
|
477
|
+
// Skip dynamic hrefs
|
|
478
|
+
if (!href.includes('${') && !href.includes('+') && !href.includes('`')) {
|
|
276
479
|
expectations.push({
|
|
277
|
-
type: '
|
|
480
|
+
type: 'navigation',
|
|
278
481
|
promise: {
|
|
279
|
-
kind: '
|
|
280
|
-
value:
|
|
482
|
+
kind: 'navigate',
|
|
483
|
+
value: href,
|
|
281
484
|
},
|
|
282
485
|
source: {
|
|
283
486
|
file: relPath,
|
|
@@ -286,24 +489,22 @@ function extractJsExpectations(content, filePath, relPath, skipped) {
|
|
|
286
489
|
},
|
|
287
490
|
confidence: 1.0,
|
|
288
491
|
});
|
|
289
|
-
} else if (!url.includes('${') && !url.includes('+') && !url.includes('`')) {
|
|
290
|
-
skipped.external++;
|
|
291
492
|
} else {
|
|
292
493
|
skipped.dynamic++;
|
|
293
494
|
}
|
|
294
495
|
}
|
|
295
496
|
|
|
296
|
-
// Extract
|
|
297
|
-
const
|
|
298
|
-
while ((match =
|
|
299
|
-
const
|
|
497
|
+
// Extract router.push("/path")
|
|
498
|
+
const routerPushRegex = /router\.push\(["']([^"']+)["']\)/g;
|
|
499
|
+
while ((match = routerPushRegex.exec(line)) !== null) {
|
|
500
|
+
const path = match[1];
|
|
300
501
|
|
|
301
|
-
if (
|
|
502
|
+
if (!path.includes('${') && !path.includes('+') && !path.includes('`')) {
|
|
302
503
|
expectations.push({
|
|
303
|
-
type: '
|
|
504
|
+
type: 'navigation',
|
|
304
505
|
promise: {
|
|
305
|
-
kind: '
|
|
306
|
-
value:
|
|
506
|
+
kind: 'navigate',
|
|
507
|
+
value: path,
|
|
307
508
|
},
|
|
308
509
|
source: {
|
|
309
510
|
file: relPath,
|
|
@@ -312,31 +513,11 @@ function extractJsExpectations(content, filePath, relPath, skipped) {
|
|
|
312
513
|
},
|
|
313
514
|
confidence: 1.0,
|
|
314
515
|
});
|
|
315
|
-
} else if (!url.includes('${') && !url.includes('+') && !url.includes('`')) {
|
|
316
|
-
skipped.external++;
|
|
317
516
|
} else {
|
|
318
517
|
skipped.dynamic++;
|
|
319
518
|
}
|
|
320
519
|
}
|
|
321
520
|
|
|
322
|
-
// Extract useState setters
|
|
323
|
-
const useStateRegex = /useState\([^)]*\)/g;
|
|
324
|
-
if ((match = useStateRegex.exec(line)) !== null) {
|
|
325
|
-
expectations.push({
|
|
326
|
-
type: 'state',
|
|
327
|
-
promise: {
|
|
328
|
-
kind: 'state_mutation',
|
|
329
|
-
value: 'state management',
|
|
330
|
-
},
|
|
331
|
-
source: {
|
|
332
|
-
file: relPath,
|
|
333
|
-
line: lineNum,
|
|
334
|
-
column: match.index,
|
|
335
|
-
},
|
|
336
|
-
confidence: 0.8,
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
|
|
340
521
|
// Extract Redux dispatch calls
|
|
341
522
|
const dispatchRegex = /dispatch\(\{/g;
|
|
342
523
|
if ((match = dispatchRegex.exec(line)) !== null) {
|
|
@@ -376,3 +557,128 @@ function extractJsExpectations(content, filePath, relPath, skipped) {
|
|
|
376
557
|
|
|
377
558
|
return expectations;
|
|
378
559
|
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* PHASE 20: Extract expectations from Vue SFC files
|
|
563
|
+
*/
|
|
564
|
+
function extractVueExpectations(content, filePath, relPath, skipped) {
|
|
565
|
+
const expectations = [];
|
|
566
|
+
|
|
567
|
+
try {
|
|
568
|
+
// Extract SFC blocks
|
|
569
|
+
const sfc = extractVueSFC(content);
|
|
570
|
+
|
|
571
|
+
// Extract template bindings
|
|
572
|
+
let templateBindings = null;
|
|
573
|
+
if (sfc.template) {
|
|
574
|
+
templateBindings = extractTemplateBindings(sfc.template.content);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Process each script block
|
|
578
|
+
for (const scriptBlock of sfc.scriptBlocks) {
|
|
579
|
+
const scriptContent = scriptBlock.content;
|
|
580
|
+
const startLine = scriptBlock.startLine;
|
|
581
|
+
|
|
582
|
+
// Map template handlers to script functions
|
|
583
|
+
let handlerMap = new Map();
|
|
584
|
+
if (templateBindings) {
|
|
585
|
+
handlerMap = mapTemplateHandlersToScript(templateBindings, [scriptBlock]);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// PHASE 20: Detect Vue navigation promises (router-link, router.push/replace)
|
|
589
|
+
const navigationPromises = detectVueNavigationPromises(
|
|
590
|
+
scriptContent,
|
|
591
|
+
filePath,
|
|
592
|
+
relPath,
|
|
593
|
+
scriptBlock,
|
|
594
|
+
templateBindings
|
|
595
|
+
);
|
|
596
|
+
|
|
597
|
+
for (const navPromise of navigationPromises) {
|
|
598
|
+
// Adjust line numbers for SFC
|
|
599
|
+
navPromise.source.line = startLine + (navPromise.source.line || 1) - 1;
|
|
600
|
+
expectations.push(navPromise);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// PHASE 20: Detect network calls in Vue script blocks
|
|
604
|
+
const networkCalls = detectNetworkCallsAST(scriptContent, filePath, relPath);
|
|
605
|
+
for (const call of networkCalls) {
|
|
606
|
+
const url = call.url;
|
|
607
|
+
|
|
608
|
+
if (url === '<dynamic>') {
|
|
609
|
+
skipped.dynamic++;
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Check if handler is UI-bound via template
|
|
614
|
+
const isUIBound = handlerMap.has(call.handlerName || '');
|
|
615
|
+
|
|
616
|
+
if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('/api/')) {
|
|
617
|
+
expectations.push({
|
|
618
|
+
type: 'network',
|
|
619
|
+
promise: {
|
|
620
|
+
kind: 'request',
|
|
621
|
+
value: url,
|
|
622
|
+
method: call.method,
|
|
623
|
+
},
|
|
624
|
+
source: {
|
|
625
|
+
file: relPath,
|
|
626
|
+
line: startLine + (call.location.line || 1) - 1,
|
|
627
|
+
column: call.location.column || 0,
|
|
628
|
+
context: call.context,
|
|
629
|
+
astSource: call.astSource || null,
|
|
630
|
+
},
|
|
631
|
+
confidence: isUIBound ? 1.0 : 0.9,
|
|
632
|
+
metadata: {
|
|
633
|
+
networkKind: call.kind,
|
|
634
|
+
isUIBound: isUIBound || false,
|
|
635
|
+
astSource: call.astSource || null,
|
|
636
|
+
},
|
|
637
|
+
});
|
|
638
|
+
} else {
|
|
639
|
+
skipped.external++;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// PHASE 20: Detect Vue state promises (ref/reactive)
|
|
644
|
+
if (templateBindings) {
|
|
645
|
+
const statePromises = detectVueStatePromises(
|
|
646
|
+
scriptContent,
|
|
647
|
+
filePath,
|
|
648
|
+
relPath,
|
|
649
|
+
scriptBlock,
|
|
650
|
+
templateBindings
|
|
651
|
+
);
|
|
652
|
+
|
|
653
|
+
for (const statePromise of statePromises) {
|
|
654
|
+
statePromise.source.line = startLine + (statePromise.source.line || 1) - 1;
|
|
655
|
+
expectations.push(statePromise);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// PHASE 20: Detect interactive elements from template
|
|
660
|
+
if (templateBindings) {
|
|
661
|
+
for (const routerLink of templateBindings.routerLinks) {
|
|
662
|
+
expectations.push({
|
|
663
|
+
type: 'navigation',
|
|
664
|
+
promise: {
|
|
665
|
+
kind: 'navigate',
|
|
666
|
+
value: routerLink.to,
|
|
667
|
+
},
|
|
668
|
+
source: {
|
|
669
|
+
file: relPath,
|
|
670
|
+
line: sfc.template.startLine,
|
|
671
|
+
column: 0,
|
|
672
|
+
context: 'template',
|
|
673
|
+
},
|
|
674
|
+
confidence: 1.0,
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
} catch (error) {
|
|
680
|
+
skipped.parseError++;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
return expectations;
|
|
684
|
+
}
|