@veraxhq/verax 0.1.0 → 0.2.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 +123 -88
- package/bin/verax.js +11 -452
- package/package.json +14 -36
- package/src/cli/commands/default.js +523 -0
- package/src/cli/commands/doctor.js +165 -0
- package/src/cli/commands/inspect.js +109 -0
- package/src/cli/commands/run.js +402 -0
- package/src/cli/entry.js +196 -0
- package/src/cli/util/atomic-write.js +37 -0
- package/src/cli/util/detection-engine.js +296 -0
- package/src/cli/util/env-url.js +33 -0
- package/src/cli/util/errors.js +44 -0
- package/src/cli/util/events.js +34 -0
- package/src/cli/util/expectation-extractor.js +378 -0
- package/src/cli/util/findings-writer.js +31 -0
- package/src/cli/util/idgen.js +87 -0
- package/src/cli/util/learn-writer.js +39 -0
- package/src/cli/util/observation-engine.js +366 -0
- package/src/cli/util/observe-writer.js +25 -0
- package/src/cli/util/paths.js +29 -0
- package/src/cli/util/project-discovery.js +277 -0
- package/src/cli/util/project-writer.js +26 -0
- package/src/cli/util/redact.js +128 -0
- package/src/cli/util/run-id.js +30 -0
- package/src/cli/util/summary-writer.js +32 -0
- package/src/verax/cli/ci-summary.js +35 -0
- package/src/verax/cli/context-explanation.js +89 -0
- package/src/verax/cli/doctor.js +277 -0
- package/src/verax/cli/error-normalizer.js +154 -0
- package/src/verax/cli/explain-output.js +105 -0
- package/src/verax/cli/finding-explainer.js +130 -0
- package/src/verax/cli/init.js +237 -0
- package/src/verax/cli/run-overview.js +163 -0
- package/src/verax/cli/url-safety.js +101 -0
- package/src/verax/cli/wizard.js +98 -0
- package/src/verax/cli/zero-findings-explainer.js +57 -0
- package/src/verax/cli/zero-interaction-explainer.js +127 -0
- package/src/verax/core/action-classifier.js +86 -0
- package/src/verax/core/budget-engine.js +218 -0
- package/src/verax/core/canonical-outcomes.js +157 -0
- package/src/verax/core/decision-snapshot.js +335 -0
- package/src/verax/core/determinism-model.js +403 -0
- package/src/verax/core/incremental-store.js +237 -0
- package/src/verax/core/invariants.js +356 -0
- package/src/verax/core/promise-model.js +230 -0
- package/src/verax/core/replay-validator.js +350 -0
- package/src/verax/core/replay.js +222 -0
- package/src/verax/core/run-id.js +175 -0
- package/src/verax/core/run-manifest.js +99 -0
- package/src/verax/core/silence-impact.js +369 -0
- package/src/verax/core/silence-model.js +521 -0
- package/src/verax/detect/comparison.js +2 -34
- package/src/verax/detect/confidence-engine.js +764 -329
- package/src/verax/detect/detection-engine.js +293 -0
- package/src/verax/detect/evidence-index.js +177 -0
- package/src/verax/detect/expectation-model.js +194 -172
- package/src/verax/detect/explanation-helpers.js +187 -0
- package/src/verax/detect/finding-detector.js +450 -0
- package/src/verax/detect/findings-writer.js +44 -8
- package/src/verax/detect/flow-detector.js +366 -0
- package/src/verax/detect/index.js +172 -286
- package/src/verax/detect/interactive-findings.js +613 -0
- package/src/verax/detect/signal-mapper.js +308 -0
- package/src/verax/detect/verdict-engine.js +563 -0
- package/src/verax/evidence-index-writer.js +61 -0
- package/src/verax/index.js +90 -14
- package/src/verax/intel/effect-detector.js +368 -0
- package/src/verax/intel/handler-mapper.js +249 -0
- package/src/verax/intel/index.js +281 -0
- package/src/verax/intel/route-extractor.js +280 -0
- package/src/verax/intel/ts-program.js +256 -0
- package/src/verax/intel/vue-navigation-extractor.js +579 -0
- package/src/verax/intel/vue-router-extractor.js +323 -0
- package/src/verax/learn/action-contract-extractor.js +335 -101
- package/src/verax/learn/ast-contract-extractor.js +95 -5
- package/src/verax/learn/flow-extractor.js +172 -0
- package/src/verax/learn/manifest-writer.js +97 -47
- package/src/verax/learn/project-detector.js +40 -0
- package/src/verax/learn/route-extractor.js +27 -96
- package/src/verax/learn/state-extractor.js +212 -0
- package/src/verax/learn/static-extractor-navigation.js +114 -0
- package/src/verax/learn/static-extractor-validation.js +88 -0
- package/src/verax/learn/static-extractor.js +112 -4
- package/src/verax/learn/truth-assessor.js +24 -21
- package/src/verax/observe/aria-sensor.js +211 -0
- package/src/verax/observe/browser.js +10 -5
- package/src/verax/observe/console-sensor.js +1 -17
- package/src/verax/observe/domain-boundary.js +10 -1
- package/src/verax/observe/expectation-executor.js +512 -0
- package/src/verax/observe/flow-matcher.js +143 -0
- package/src/verax/observe/focus-sensor.js +196 -0
- package/src/verax/observe/human-driver.js +643 -275
- package/src/verax/observe/index.js +908 -27
- package/src/verax/observe/index.js.backup +1 -0
- package/src/verax/observe/interaction-discovery.js +365 -14
- package/src/verax/observe/interaction-runner.js +563 -198
- package/src/verax/observe/loading-sensor.js +139 -0
- package/src/verax/observe/navigation-sensor.js +255 -0
- package/src/verax/observe/network-sensor.js +55 -7
- package/src/verax/observe/observed-expectation-deriver.js +186 -0
- package/src/verax/observe/observed-expectation.js +305 -0
- package/src/verax/observe/page-frontier.js +234 -0
- package/src/verax/observe/settle.js +37 -17
- package/src/verax/observe/state-sensor.js +389 -0
- package/src/verax/observe/timing-sensor.js +228 -0
- package/src/verax/observe/traces-writer.js +61 -20
- package/src/verax/observe/ui-signal-sensor.js +136 -17
- package/src/verax/scan-summary-writer.js +77 -15
- package/src/verax/shared/artifact-manager.js +110 -8
- package/src/verax/shared/budget-profiles.js +136 -0
- package/src/verax/shared/ci-detection.js +39 -0
- package/src/verax/shared/config-loader.js +170 -0
- package/src/verax/shared/dynamic-route-utils.js +218 -0
- package/src/verax/shared/expectation-coverage.js +44 -0
- package/src/verax/shared/expectation-prover.js +81 -0
- package/src/verax/shared/expectation-tracker.js +201 -0
- package/src/verax/shared/expectations-writer.js +60 -0
- package/src/verax/shared/first-run.js +44 -0
- package/src/verax/shared/progress-reporter.js +171 -0
- package/src/verax/shared/retry-policy.js +14 -1
- package/src/verax/shared/root-artifacts.js +49 -0
- package/src/verax/shared/scan-budget.js +86 -0
- package/src/verax/shared/url-normalizer.js +162 -0
- package/src/verax/shared/zip-artifacts.js +65 -0
- package/src/verax/validate/context-validator.js +244 -0
- package/src/verax/validate/context-validator.js.bak +0 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FLOW INTELLIGENCE v1 — Flow Extractor
|
|
3
|
+
*
|
|
4
|
+
* Extracts multi-step flows from PROVEN expectations.
|
|
5
|
+
* NO HEURISTICS: Only sequences with explicit PROVEN expectations qualify as flows.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createHash } from 'crypto';
|
|
9
|
+
import { isProvenExpectation } from '../shared/expectation-prover.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Generate stable flow ID from ordered steps
|
|
13
|
+
*/
|
|
14
|
+
function generateFlowId(steps) {
|
|
15
|
+
const hashInput = steps.map(s => `${s.expectationType}:${s.source || s.handlerRef}`).join('|');
|
|
16
|
+
const hash = createHash('sha256').update(hashInput).digest('hex');
|
|
17
|
+
return `flow-${hash.substring(0, 8)}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Determine if an expectation can be part of a flow.
|
|
22
|
+
* Only PROVEN expectations with clear sequential intent qualify.
|
|
23
|
+
*/
|
|
24
|
+
function isFlowableExpectation(exp) {
|
|
25
|
+
// Navigation expectations: always flowable (user journey)
|
|
26
|
+
if (exp.expectationType === 'navigation') return true;
|
|
27
|
+
|
|
28
|
+
// Network actions: flowable (form submissions, API calls)
|
|
29
|
+
if (exp.kind === 'NETWORK_ACTION') return true;
|
|
30
|
+
|
|
31
|
+
// State actions: flowable (state updates in sequence)
|
|
32
|
+
if (exp.kind === 'STATE_ACTION') return true;
|
|
33
|
+
|
|
34
|
+
// Validation blocks: NOT flowable alone (they block, don't progress)
|
|
35
|
+
if (exp.kind === 'VALIDATION_BLOCK') return false;
|
|
36
|
+
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Extract flows from manifest expectations.
|
|
42
|
+
*
|
|
43
|
+
* Flow definition:
|
|
44
|
+
* - Sequence of 2-5 PROVEN expectations
|
|
45
|
+
* - Temporally ordered within same route/page
|
|
46
|
+
* - Each step must be deterministically executable
|
|
47
|
+
*
|
|
48
|
+
* @param {Object} manifest - Learned manifest
|
|
49
|
+
* @returns {Array} Array of flow definitions
|
|
50
|
+
*/
|
|
51
|
+
export function extractFlows(manifest) {
|
|
52
|
+
const flows = [];
|
|
53
|
+
|
|
54
|
+
// Collect all flowable expectations
|
|
55
|
+
const flowableExpectations = [];
|
|
56
|
+
|
|
57
|
+
// SPA expectations (navigation)
|
|
58
|
+
if (manifest.spaExpectations && manifest.spaExpectations.length > 0) {
|
|
59
|
+
for (const exp of manifest.spaExpectations) {
|
|
60
|
+
if (isFlowableExpectation(exp)) {
|
|
61
|
+
flowableExpectations.push({
|
|
62
|
+
source: exp.source || exp.sourceFile,
|
|
63
|
+
expectationType: 'navigation',
|
|
64
|
+
targetPath: exp.targetPath,
|
|
65
|
+
proof: 'PROVEN_EXPECTATION',
|
|
66
|
+
raw: exp
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Action contracts (network, state)
|
|
73
|
+
if (manifest.actionContracts && manifest.actionContracts.length > 0) {
|
|
74
|
+
for (const contract of manifest.actionContracts) {
|
|
75
|
+
if (isFlowableExpectation(contract)) {
|
|
76
|
+
flowableExpectations.push({
|
|
77
|
+
source: contract.source,
|
|
78
|
+
handlerRef: contract.handlerRef,
|
|
79
|
+
expectationType: contract.kind === 'NETWORK_ACTION' ? 'network_action' : 'state_action',
|
|
80
|
+
method: contract.method,
|
|
81
|
+
urlPath: contract.urlPath,
|
|
82
|
+
proof: 'PROVEN_EXPECTATION',
|
|
83
|
+
raw: contract
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Static expectations (navigation, network_action, form submissions)
|
|
90
|
+
if (manifest.staticExpectations && manifest.staticExpectations.length > 0) {
|
|
91
|
+
for (const exp of manifest.staticExpectations) {
|
|
92
|
+
// FLOW INTELLIGENCE v1: Only include PROVEN expectations
|
|
93
|
+
if (!isProvenExpectation(exp)) continue;
|
|
94
|
+
|
|
95
|
+
let expType = null;
|
|
96
|
+
if (exp.type === 'navigation' || exp.type === 'spa_navigation') {
|
|
97
|
+
expType = 'navigation';
|
|
98
|
+
} else if (exp.type === 'network_action') {
|
|
99
|
+
expType = 'network_action';
|
|
100
|
+
} else if (exp.type === 'form_submission') {
|
|
101
|
+
expType = 'network_action'; // Form submissions are network actions
|
|
102
|
+
} else {
|
|
103
|
+
continue; // Skip non-flowable types
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
flowableExpectations.push({
|
|
107
|
+
source: exp.evidence?.source || exp.fromPath || exp.metadata?.elementFile,
|
|
108
|
+
handlerRef: exp.handlerRef || exp.metadata?.handlerFile,
|
|
109
|
+
expectationType: expType,
|
|
110
|
+
targetPath: exp.targetPath || exp.expectedTarget,
|
|
111
|
+
urlPath: exp.urlPath || exp.expectedTarget,
|
|
112
|
+
method: exp.method,
|
|
113
|
+
proof: 'PROVEN_EXPECTATION',
|
|
114
|
+
raw: exp
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// DETERMINISTIC FLOW DETECTION: Group by source page
|
|
120
|
+
// Flows are sequences on the same page (same source file/route)
|
|
121
|
+
const bySource = new Map();
|
|
122
|
+
|
|
123
|
+
for (const exp of flowableExpectations) {
|
|
124
|
+
// Extract file name from source (e.g., "index.html" from "index.html:56:68")
|
|
125
|
+
let sourceKey = 'unknown';
|
|
126
|
+
if (exp.source) {
|
|
127
|
+
sourceKey = exp.source.split(':')[0]; // Get filename part
|
|
128
|
+
} else if (exp.handlerRef) {
|
|
129
|
+
sourceKey = exp.handlerRef.split(':')[0];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!bySource.has(sourceKey)) {
|
|
133
|
+
bySource.set(sourceKey, []);
|
|
134
|
+
}
|
|
135
|
+
bySource.get(sourceKey).push(exp);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// For each source, create flows of 2-5 sequential expectations
|
|
139
|
+
for (const [source, expectations] of bySource.entries()) {
|
|
140
|
+
if (expectations.length < 2) continue; // Need at least 2 for a flow
|
|
141
|
+
|
|
142
|
+
// Take sequences of 2-5 expectations as potential flows
|
|
143
|
+
const maxFlowLength = Math.min(5, expectations.length);
|
|
144
|
+
|
|
145
|
+
for (let length = 2; length <= maxFlowLength; length++) {
|
|
146
|
+
// Only create one flow per source (the full sequence up to 5 steps)
|
|
147
|
+
if (length === Math.min(expectations.length, 5)) {
|
|
148
|
+
const flowSteps = expectations.slice(0, length).map((exp, idx) => ({
|
|
149
|
+
stepIndex: idx,
|
|
150
|
+
expectationType: exp.expectationType,
|
|
151
|
+
source: exp.source,
|
|
152
|
+
handlerRef: exp.handlerRef,
|
|
153
|
+
targetPath: exp.targetPath,
|
|
154
|
+
method: exp.method,
|
|
155
|
+
urlPath: exp.urlPath
|
|
156
|
+
}));
|
|
157
|
+
|
|
158
|
+
const flowId = generateFlowId(flowSteps);
|
|
159
|
+
|
|
160
|
+
flows.push({
|
|
161
|
+
flowId,
|
|
162
|
+
source,
|
|
163
|
+
stepCount: length,
|
|
164
|
+
steps: flowSteps,
|
|
165
|
+
proof: 'PROVEN_EXPECTATION'
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return flows;
|
|
172
|
+
}
|
|
@@ -2,10 +2,11 @@ import { resolve } from 'path';
|
|
|
2
2
|
import { writeFileSync, mkdirSync } from 'fs';
|
|
3
3
|
import { extractStaticExpectations } from './static-extractor.js';
|
|
4
4
|
import { assessLearnTruth } from './truth-assessor.js';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
5
|
+
import { runCodeIntelligence } from '../intel/index.js';
|
|
6
|
+
import { isProvenExpectation } from '../shared/expectation-prover.js';
|
|
7
|
+
import { extractFlows } from './flow-extractor.js';
|
|
8
|
+
import { createTSProgram } from '../intel/ts-program.js';
|
|
9
|
+
import { extractVueNavigationPromises } from '../intel/vue-navigation-extractor.js';
|
|
9
10
|
|
|
10
11
|
export async function writeManifest(projectDir, projectType, routes) {
|
|
11
12
|
const publicRoutes = routes.filter(r => r.public).map(r => r.path);
|
|
@@ -19,7 +20,8 @@ export async function writeManifest(projectDir, projectType, routes) {
|
|
|
19
20
|
routes: routes.map(r => ({
|
|
20
21
|
path: r.path,
|
|
21
22
|
source: r.source,
|
|
22
|
-
public: r.public
|
|
23
|
+
public: r.public,
|
|
24
|
+
sourceRef: r.sourceRef || null
|
|
23
25
|
})),
|
|
24
26
|
publicRoutes: publicRoutes,
|
|
25
27
|
internalRoutes: internalRoutes,
|
|
@@ -27,49 +29,110 @@ export async function writeManifest(projectDir, projectType, routes) {
|
|
|
27
29
|
};
|
|
28
30
|
|
|
29
31
|
let staticExpectations = null;
|
|
30
|
-
let
|
|
31
|
-
let
|
|
32
|
+
let intelExpectations = [];
|
|
33
|
+
let allExpectations = [];
|
|
32
34
|
|
|
35
|
+
// Static sites: extract from HTML
|
|
33
36
|
if (projectType === 'static' && routes.length > 0) {
|
|
34
37
|
staticExpectations = await extractStaticExpectations(projectDir, routes);
|
|
35
38
|
manifest.staticExpectations = staticExpectations;
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
allExpectations = staticExpectations || [];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Vue projects: extract navigation promises from code
|
|
43
|
+
if (projectType === 'vue_router' || projectType === 'vue_spa') {
|
|
44
|
+
const program = createTSProgram(projectDir, { includeJs: true });
|
|
41
45
|
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
if (!program.error) {
|
|
47
|
+
const vueNavPromises = extractVueNavigationPromises(program, projectDir);
|
|
48
|
+
|
|
49
|
+
if (vueNavPromises && vueNavPromises.length > 0) {
|
|
50
|
+
intelExpectations = vueNavPromises.filter(exp => isProvenExpectation(exp));
|
|
51
|
+
|
|
52
|
+
if (!manifest.staticExpectations) {
|
|
53
|
+
manifest.staticExpectations = [];
|
|
54
|
+
}
|
|
55
|
+
manifest.staticExpectations.push(...intelExpectations);
|
|
56
|
+
allExpectations = intelExpectations;
|
|
57
|
+
}
|
|
47
58
|
}
|
|
48
|
-
} else {
|
|
49
|
-
manifest.expectationProof = ExpectationProof.UNKNOWN_EXPECTATION;
|
|
50
59
|
}
|
|
51
60
|
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
// React SPAs, Next.js, and Vue: use code intelligence (AST-based) - NO FALLBACKS
|
|
62
|
+
if (projectType === 'react_spa' ||
|
|
63
|
+
projectType === 'nextjs_app_router' ||
|
|
64
|
+
projectType === 'nextjs_pages_router' ||
|
|
65
|
+
projectType === 'vue_router' ||
|
|
66
|
+
projectType === 'vue_spa') {
|
|
67
|
+
const intelResult = await runCodeIntelligence(projectDir);
|
|
68
|
+
|
|
69
|
+
if (!intelResult.error && intelResult.expectations) {
|
|
70
|
+
// ZERO-HEURISTIC: Only include expectations that pass isProvenExpectation()
|
|
71
|
+
intelExpectations = intelResult.expectations.filter(exp => isProvenExpectation(exp));
|
|
72
|
+
|
|
73
|
+
// STATE INTELLIGENCE: Also extract state expectations from AST
|
|
74
|
+
const { extractStateExpectationsFromAST } = await import('./state-extractor.js');
|
|
75
|
+
const stateResult = await extractStateExpectationsFromAST(projectDir);
|
|
76
|
+
|
|
77
|
+
if (stateResult.expectations && stateResult.expectations.length > 0) {
|
|
78
|
+
// Convert state expectations to manifest format with sourceRef
|
|
79
|
+
const stateExpectations = stateResult.expectations.map(exp => ({
|
|
80
|
+
type: 'state_action',
|
|
81
|
+
expectedTarget: exp.expectedTarget,
|
|
82
|
+
storeType: exp.storeType,
|
|
83
|
+
proof: 'PROVEN_EXPECTATION',
|
|
84
|
+
sourceRef: exp.line ? `${exp.sourceFile}:${exp.line}` : exp.sourceFile,
|
|
85
|
+
selectorHint: null, // State actions don't have selector hints from AST
|
|
86
|
+
metadata: {
|
|
87
|
+
sourceFile: exp.sourceFile,
|
|
88
|
+
line: exp.line
|
|
89
|
+
}
|
|
90
|
+
})).filter(exp => isProvenExpectation(exp));
|
|
91
|
+
|
|
92
|
+
intelExpectations.push(...stateExpectations);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Add intel expectations to manifest
|
|
96
|
+
if (!manifest.staticExpectations) {
|
|
97
|
+
manifest.staticExpectations = [];
|
|
98
|
+
}
|
|
99
|
+
manifest.staticExpectations.push(...intelExpectations);
|
|
100
|
+
allExpectations = intelExpectations;
|
|
63
101
|
}
|
|
64
102
|
}
|
|
65
|
-
|
|
66
|
-
|
|
103
|
+
|
|
104
|
+
// ZERO-HEURISTIC: Set expectationsStatus and coverageGaps
|
|
105
|
+
const provenCount = allExpectations.filter(exp => isProvenExpectation(exp)).length;
|
|
106
|
+
|
|
107
|
+
if (provenCount === 0 && (projectType === 'react_spa' ||
|
|
108
|
+
projectType === 'nextjs_app_router' ||
|
|
109
|
+
projectType === 'nextjs_pages_router' ||
|
|
110
|
+
projectType === 'vue_router' ||
|
|
111
|
+
projectType === 'vue_spa')) {
|
|
112
|
+
manifest.expectationsStatus = 'NO_PROVEN_EXPECTATIONS';
|
|
113
|
+
manifest.coverageGaps = [{
|
|
114
|
+
reason: 'NO_PROVEN_EXPECTATIONS_AVAILABLE',
|
|
115
|
+
message: 'Code intelligence found no extractable literal effects (navigation, network, validation) with static string literals',
|
|
116
|
+
projectType: projectType
|
|
117
|
+
}];
|
|
118
|
+
} else {
|
|
119
|
+
manifest.expectationsStatus = 'PROVEN_EXPECTATIONS_AVAILABLE';
|
|
120
|
+
manifest.coverageGaps = [];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// FLOW INTELLIGENCE v1: Extract flows from PROVEN expectations
|
|
124
|
+
const flows = extractFlows(manifest);
|
|
125
|
+
if (flows.length > 0) {
|
|
126
|
+
manifest.flows = flows;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const learnTruth = await assessLearnTruth(projectDir, projectType, routes, allExpectations);
|
|
67
130
|
manifest.notes.push({
|
|
68
131
|
type: 'truth',
|
|
69
132
|
learn: learnTruth
|
|
70
133
|
});
|
|
71
134
|
|
|
72
|
-
const manifestDir = resolve(projectDir, '.
|
|
135
|
+
const manifestDir = resolve(projectDir, '.veraxverax', 'learn');
|
|
73
136
|
mkdirSync(manifestDir, { recursive: true });
|
|
74
137
|
|
|
75
138
|
const manifestPath = resolve(manifestDir, 'site-manifest.json');
|
|
@@ -78,20 +141,7 @@ export async function writeManifest(projectDir, projectType, routes) {
|
|
|
78
141
|
return {
|
|
79
142
|
...manifest,
|
|
80
143
|
manifestPath: manifestPath,
|
|
81
|
-
learnTruth: learnTruth
|
|
82
|
-
actionContracts
|
|
144
|
+
learnTruth: learnTruth
|
|
83
145
|
};
|
|
84
146
|
}
|
|
85
147
|
|
|
86
|
-
function dedupeActionContracts(contracts) {
|
|
87
|
-
const seen = new Set();
|
|
88
|
-
const out = [];
|
|
89
|
-
for (const c of contracts) {
|
|
90
|
-
const key = `${c.kind}|${c.method}|${c.urlPath}|${c.source || ''}|${c.handlerRef || ''}`;
|
|
91
|
-
if (seen.has(key)) continue;
|
|
92
|
-
seen.add(key);
|
|
93
|
-
out.push(c);
|
|
94
|
-
}
|
|
95
|
-
return out;
|
|
96
|
-
}
|
|
97
|
-
|
|
@@ -44,6 +44,34 @@ async function hasReactRouter(projectDir) {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
async function hasVue(projectDir) {
|
|
48
|
+
try {
|
|
49
|
+
const packageJsonPath = resolve(projectDir, 'package.json');
|
|
50
|
+
if (!existsSync(packageJsonPath)) return false;
|
|
51
|
+
|
|
52
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
53
|
+
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
54
|
+
|
|
55
|
+
return !!deps.vue;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function hasVueRouter(projectDir) {
|
|
62
|
+
try {
|
|
63
|
+
const packageJsonPath = resolve(projectDir, 'package.json');
|
|
64
|
+
if (!existsSync(packageJsonPath)) return false;
|
|
65
|
+
|
|
66
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
67
|
+
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
68
|
+
|
|
69
|
+
return !!deps['vue-router'];
|
|
70
|
+
} catch (error) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
47
75
|
export async function detectProjectType(projectDir) {
|
|
48
76
|
const appDir = resolve(projectDir, 'app');
|
|
49
77
|
const pagesDir = resolve(projectDir, 'pages');
|
|
@@ -62,6 +90,17 @@ export async function detectProjectType(projectDir) {
|
|
|
62
90
|
return 'nextjs_pages_router';
|
|
63
91
|
}
|
|
64
92
|
}
|
|
93
|
+
|
|
94
|
+
const hasVueDep = await hasVue(projectDir);
|
|
95
|
+
const hasVueRouterDep = await hasVueRouter(projectDir);
|
|
96
|
+
|
|
97
|
+
if (hasVueDep && hasVueRouterDep) {
|
|
98
|
+
return 'vue_router';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (hasVueDep && !hasVueRouterDep) {
|
|
102
|
+
return 'vue_spa';
|
|
103
|
+
}
|
|
65
104
|
|
|
66
105
|
const hasReact = await hasReactDependency(projectDir);
|
|
67
106
|
const hasNext = await hasNextJs(projectDir);
|
|
@@ -85,3 +124,4 @@ export async function hasReactRouterDom(projectDir) {
|
|
|
85
124
|
return await hasReactRouter(projectDir);
|
|
86
125
|
}
|
|
87
126
|
|
|
127
|
+
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { glob } from 'glob';
|
|
2
1
|
import { resolve } from 'path';
|
|
2
|
+
import { extractStaticRoutes } from './static-extractor.js';
|
|
3
|
+
import { createTSProgram } from '../intel/ts-program.js';
|
|
4
|
+
import { extractRoutes as extractRoutesAST } from '../intel/route-extractor.js';
|
|
3
5
|
|
|
4
6
|
const INTERNAL_PATH_PATTERNS = [
|
|
5
7
|
/^\/admin/,
|
|
@@ -14,109 +16,38 @@ function isInternalRoute(path) {
|
|
|
14
16
|
return INTERNAL_PATH_PATTERNS.some(pattern => pattern.test(path));
|
|
15
17
|
}
|
|
16
18
|
|
|
17
|
-
function fileToAppRouterPath(file) {
|
|
18
|
-
let path = file.replace(/[\\\/]/g, '/');
|
|
19
|
-
path = path.replace(/(^|\/)page\.(js|jsx|ts|tsx)$/, '');
|
|
20
|
-
path = path.replace(/^\./, '');
|
|
21
|
-
|
|
22
|
-
if (path === '' || path === '/') {
|
|
23
|
-
return '/';
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (!path.startsWith('/')) {
|
|
27
|
-
path = '/' + path;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
path = path.replace(/\[([^\]]+)\]/g, ':$1');
|
|
31
|
-
path = path.replace(/\([^)]+\)/g, '');
|
|
32
|
-
|
|
33
|
-
return path || '/';
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function fileToPagesRouterPath(file) {
|
|
37
|
-
let path = file.replace(/[\\\/]/g, '/');
|
|
38
|
-
path = path.replace(/\.(js|jsx|ts|tsx)$/, '');
|
|
39
|
-
path = path.replace(/^index$/, '');
|
|
40
|
-
path = path.replace(/^\./, '');
|
|
41
|
-
|
|
42
|
-
if (path === '' || path === '/') {
|
|
43
|
-
return '/';
|
|
44
|
-
}
|
|
45
|
-
if (!path.startsWith('/')) {
|
|
46
|
-
path = '/' + path;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
path = path.replace(/\[([^\]]+)\]/g, ':$1');
|
|
50
|
-
|
|
51
|
-
return path || '/';
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
import { extractStaticRoutes } from './static-extractor.js';
|
|
55
|
-
import { extractReactRouterRoutes } from './react-router-extractor.js';
|
|
56
|
-
import { hasReactRouterDom } from './project-detector.js';
|
|
57
|
-
|
|
58
19
|
export async function extractRoutes(projectDir, projectType) {
|
|
59
|
-
|
|
60
|
-
const routeSet = new Set();
|
|
61
|
-
|
|
20
|
+
// Static sites: use file-based extractor (no regex, just file system)
|
|
62
21
|
if (projectType === 'static') {
|
|
63
22
|
return await extractStaticRoutes(projectDir);
|
|
64
23
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (projectType === 'nextjs_app_router') {
|
|
75
|
-
const appDir = resolve(projectDir, 'app');
|
|
76
|
-
const pageFiles = await glob('**/page.{js,jsx,ts,tsx}', {
|
|
77
|
-
cwd: appDir,
|
|
78
|
-
absolute: false,
|
|
79
|
-
ignore: ['node_modules/**']
|
|
80
|
-
});
|
|
24
|
+
|
|
25
|
+
// React SPAs, Next.js, and Vue: use AST-based intel module (NO REGEX)
|
|
26
|
+
if (projectType === 'react_spa' ||
|
|
27
|
+
projectType === 'nextjs_app_router' ||
|
|
28
|
+
projectType === 'nextjs_pages_router' ||
|
|
29
|
+
projectType === 'vue_router' ||
|
|
30
|
+
projectType === 'vue_spa') {
|
|
31
|
+
const program = createTSProgram(projectDir, { includeJs: true });
|
|
81
32
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (!routeSet.has(routeKey)) {
|
|
87
|
-
routeSet.add(routeKey);
|
|
88
|
-
routes.push({
|
|
89
|
-
path: routePath,
|
|
90
|
-
source: `app/${file}`,
|
|
91
|
-
public: !isInternalRoute(routePath)
|
|
92
|
-
});
|
|
93
|
-
}
|
|
33
|
+
if (program.error) {
|
|
34
|
+
// Fallback: return empty routes if TS program creation fails
|
|
35
|
+
return [];
|
|
94
36
|
}
|
|
95
|
-
} else if (projectType === 'nextjs_pages_router') {
|
|
96
|
-
const pagesDir = resolve(projectDir, 'pages');
|
|
97
|
-
const pageFiles = await glob('**/*.{js,jsx,ts,tsx}', {
|
|
98
|
-
cwd: pagesDir,
|
|
99
|
-
absolute: false,
|
|
100
|
-
ignore: ['node_modules/**', '_app.*', '_document.*', '_error.*']
|
|
101
|
-
});
|
|
102
37
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
}
|
|
38
|
+
const astRoutes = extractRoutesAST(projectDir, program);
|
|
39
|
+
|
|
40
|
+
// Convert AST routes to manifest format and sort for determinism
|
|
41
|
+
const routes = astRoutes.map(r => ({
|
|
42
|
+
path: r.path,
|
|
43
|
+
source: r.sourceRef || r.file || 'unknown',
|
|
44
|
+
public: r.public !== undefined ? r.public : !isInternalRoute(r.path),
|
|
45
|
+
sourceRef: r.sourceRef
|
|
46
|
+
}));
|
|
47
|
+
routes.sort((a, b) => a.path.localeCompare(b.path));
|
|
48
|
+
return routes;
|
|
116
49
|
}
|
|
117
50
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return routes;
|
|
51
|
+
return [];
|
|
121
52
|
}
|
|
122
53
|
|