@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
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
import { parse } from '@babel/parser';
|
|
2
|
+
import _traverse from '@babel/traverse';
|
|
3
|
+
|
|
4
|
+
// Handle default export from @babel/traverse (CommonJS/ESM compatibility)
|
|
5
|
+
const traverse = _traverse.default || _traverse;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* PHASE 10 — Professional useState Tracking
|
|
9
|
+
*
|
|
10
|
+
* AST-based React useState detection with:
|
|
11
|
+
* - Context tracking (handler/hook/function)
|
|
12
|
+
* - AST source extraction for evidence
|
|
13
|
+
* - State ↔ UI correlation
|
|
14
|
+
* - False positive prevention
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Detect useState patterns and their UI connections
|
|
19
|
+
* PHASE 10: Enhanced with context tracking and AST source extraction
|
|
20
|
+
* @param {string} content - File content
|
|
21
|
+
* @param {string} filePath - Absolute file path
|
|
22
|
+
* @param {string} relPath - Relative path from source root
|
|
23
|
+
* @returns {Array} Array of state-driven UI promises with context and AST source
|
|
24
|
+
*/
|
|
25
|
+
export function detectUseStatePromises(content, filePath, relPath) {
|
|
26
|
+
const promises = [];
|
|
27
|
+
const lines = content.split('\n');
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const ast = parse(content, {
|
|
31
|
+
sourceType: 'module',
|
|
32
|
+
plugins: [
|
|
33
|
+
'jsx',
|
|
34
|
+
'typescript',
|
|
35
|
+
'classProperties',
|
|
36
|
+
'optionalChaining',
|
|
37
|
+
'nullishCoalescingOperator',
|
|
38
|
+
'dynamicImport',
|
|
39
|
+
['decorators', { decoratorsBeforeExport: true }],
|
|
40
|
+
'topLevelAwait',
|
|
41
|
+
'objectRestSpread',
|
|
42
|
+
'asyncGenerators',
|
|
43
|
+
],
|
|
44
|
+
errorRecovery: true,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Track useState imports from 'react'
|
|
48
|
+
const useStateImported = new Set();
|
|
49
|
+
|
|
50
|
+
// Track all state declarations: { stateName, setterName, componentName, loc }
|
|
51
|
+
const stateDeclarations = [];
|
|
52
|
+
|
|
53
|
+
// PHASE 10: Enhanced setter calls with context and AST source
|
|
54
|
+
// Track setter calls: { setterName, stateName, loc, isUpdaterFunction, context, astSource, isUIBound }
|
|
55
|
+
const setterCalls = [];
|
|
56
|
+
|
|
57
|
+
// Track JSX usage of state variables: { stateName, loc, usageType }
|
|
58
|
+
const jsxUsages = [];
|
|
59
|
+
|
|
60
|
+
traverse(ast, {
|
|
61
|
+
// Track useState imports
|
|
62
|
+
ImportDeclaration(path) {
|
|
63
|
+
if (path.node.source.value === 'react') {
|
|
64
|
+
path.node.specifiers.forEach((spec) => {
|
|
65
|
+
if (spec.type === 'ImportSpecifier' && spec.imported.name === 'useState') {
|
|
66
|
+
useStateImported.add(spec.local.name);
|
|
67
|
+
}
|
|
68
|
+
// Handle: import React from 'react'
|
|
69
|
+
if (spec.type === 'ImportDefaultSpecifier') {
|
|
70
|
+
useStateImported.add('React.useState');
|
|
71
|
+
}
|
|
72
|
+
// Handle: import * as React from 'react'
|
|
73
|
+
if (spec.type === 'ImportNamespaceSpecifier') {
|
|
74
|
+
useStateImported.add(`${spec.local.name}.useState`);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
// Detect useState declarations
|
|
81
|
+
VariableDeclarator(path) {
|
|
82
|
+
const { node } = path;
|
|
83
|
+
const loc = node.loc;
|
|
84
|
+
|
|
85
|
+
// Check if init is a useState call
|
|
86
|
+
if (node.init?.type === 'CallExpression') {
|
|
87
|
+
const callee = node.init.callee;
|
|
88
|
+
let isUseState = false;
|
|
89
|
+
|
|
90
|
+
// Direct: useState(...)
|
|
91
|
+
if (callee.type === 'Identifier' && useStateImported.has(callee.name)) {
|
|
92
|
+
isUseState = true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// React.useState(...)
|
|
96
|
+
if (callee.type === 'MemberExpression' &&
|
|
97
|
+
callee.object.name === 'React' &&
|
|
98
|
+
callee.property.name === 'useState') {
|
|
99
|
+
isUseState = true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (isUseState && node.id.type === 'ArrayPattern') {
|
|
103
|
+
// Extract [state, setState]
|
|
104
|
+
const elements = node.id.elements;
|
|
105
|
+
if (elements.length >= 2) {
|
|
106
|
+
const stateVar = elements[0];
|
|
107
|
+
const setterVar = elements[1];
|
|
108
|
+
|
|
109
|
+
if (stateVar?.type === 'Identifier' && setterVar?.type === 'Identifier') {
|
|
110
|
+
const stateName = stateVar.name;
|
|
111
|
+
const setterName = setterVar.name;
|
|
112
|
+
|
|
113
|
+
// Find component name
|
|
114
|
+
const componentName = findComponentName(path);
|
|
115
|
+
|
|
116
|
+
stateDeclarations.push({
|
|
117
|
+
stateName,
|
|
118
|
+
setterName,
|
|
119
|
+
componentName,
|
|
120
|
+
location: {
|
|
121
|
+
line: loc?.start.line,
|
|
122
|
+
column: loc?.start.column,
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
// PHASE 10: Detect setter calls with context tracking and AST source
|
|
132
|
+
CallExpression(path) {
|
|
133
|
+
const { node } = path;
|
|
134
|
+
const loc = node.loc;
|
|
135
|
+
|
|
136
|
+
if (node.callee.type === 'Identifier') {
|
|
137
|
+
const calleeName = node.callee.name;
|
|
138
|
+
|
|
139
|
+
// Check if this identifier matches any known setter
|
|
140
|
+
const matchingState = stateDeclarations.find(s => s.setterName === calleeName);
|
|
141
|
+
if (matchingState) {
|
|
142
|
+
// Check if it's an updater function: setX(prev => next)
|
|
143
|
+
const isUpdaterFunction = node.arguments.length > 0 &&
|
|
144
|
+
(node.arguments[0].type === 'ArrowFunctionExpression' ||
|
|
145
|
+
node.arguments[0].type === 'FunctionExpression');
|
|
146
|
+
|
|
147
|
+
// PHASE 10: Infer context (handler/hook/function) - reuse Phase 9 style
|
|
148
|
+
const context = inferContext(path);
|
|
149
|
+
const isUIBound = isUIBoundHandler(path);
|
|
150
|
+
|
|
151
|
+
// PHASE 10: Extract AST source code for evidence
|
|
152
|
+
const astSource = extractASTSource(node, lines, loc);
|
|
153
|
+
|
|
154
|
+
setterCalls.push({
|
|
155
|
+
setterName: calleeName,
|
|
156
|
+
stateName: matchingState.stateName,
|
|
157
|
+
location: {
|
|
158
|
+
line: loc?.start.line,
|
|
159
|
+
column: loc?.start.column,
|
|
160
|
+
},
|
|
161
|
+
isUpdaterFunction,
|
|
162
|
+
context, // PHASE 10: Context tracking
|
|
163
|
+
astSource, // PHASE 10: AST source for evidence
|
|
164
|
+
isUIBound, // PHASE 10: UI-bound detection
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
// Detect state usage in JSX
|
|
171
|
+
JSXExpressionContainer(path) {
|
|
172
|
+
const { node } = path;
|
|
173
|
+
const loc = node.loc;
|
|
174
|
+
|
|
175
|
+
// Check if expression references any state variable
|
|
176
|
+
if (node.expression.type === 'Identifier') {
|
|
177
|
+
const identifierName = node.expression.name;
|
|
178
|
+
const matchingState = stateDeclarations.find(s => s.stateName === identifierName);
|
|
179
|
+
|
|
180
|
+
if (matchingState) {
|
|
181
|
+
const usageType = inferJSXUsageType(path);
|
|
182
|
+
jsxUsages.push({
|
|
183
|
+
stateName: identifierName,
|
|
184
|
+
location: {
|
|
185
|
+
line: loc?.start.line,
|
|
186
|
+
column: loc?.start.column,
|
|
187
|
+
},
|
|
188
|
+
usageType,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Check member expressions: {state.property}
|
|
194
|
+
if (node.expression.type === 'MemberExpression' &&
|
|
195
|
+
node.expression.object.type === 'Identifier') {
|
|
196
|
+
const identifierName = node.expression.object.name;
|
|
197
|
+
const matchingState = stateDeclarations.find(s => s.stateName === identifierName);
|
|
198
|
+
|
|
199
|
+
if (matchingState) {
|
|
200
|
+
const usageType = inferJSXUsageType(path);
|
|
201
|
+
jsxUsages.push({
|
|
202
|
+
stateName: identifierName,
|
|
203
|
+
location: {
|
|
204
|
+
line: loc?.start.line,
|
|
205
|
+
column: loc?.start.column,
|
|
206
|
+
},
|
|
207
|
+
usageType,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Check expressions like: {loading ? 'Loading...' : 'Done'}
|
|
213
|
+
if (node.expression.type === 'ConditionalExpression') {
|
|
214
|
+
const testIdentifiers = extractIdentifiers(node.expression.test);
|
|
215
|
+
testIdentifiers.forEach(name => {
|
|
216
|
+
const matchingState = stateDeclarations.find(s => s.stateName === name);
|
|
217
|
+
if (matchingState) {
|
|
218
|
+
jsxUsages.push({
|
|
219
|
+
stateName: name,
|
|
220
|
+
location: {
|
|
221
|
+
line: loc?.start.line,
|
|
222
|
+
column: loc?.start.column,
|
|
223
|
+
},
|
|
224
|
+
usageType: 'conditional-rendering',
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Check logical expressions: {loading && <Spinner />}
|
|
231
|
+
if (node.expression.type === 'LogicalExpression') {
|
|
232
|
+
const leftIdentifiers = extractIdentifiers(node.expression.left);
|
|
233
|
+
leftIdentifiers.forEach(name => {
|
|
234
|
+
const matchingState = stateDeclarations.find(s => s.stateName === name);
|
|
235
|
+
if (matchingState) {
|
|
236
|
+
jsxUsages.push({
|
|
237
|
+
stateName: name,
|
|
238
|
+
location: {
|
|
239
|
+
line: loc?.start.line,
|
|
240
|
+
column: loc?.start.column,
|
|
241
|
+
},
|
|
242
|
+
usageType: 'conditional-rendering',
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Check call expressions in JSX: {items.map(...)}
|
|
249
|
+
if (node.expression.type === 'CallExpression' &&
|
|
250
|
+
node.expression.callee.type === 'MemberExpression' &&
|
|
251
|
+
node.expression.callee.object.type === 'Identifier') {
|
|
252
|
+
const identifierName = node.expression.callee.object.name;
|
|
253
|
+
const matchingState = stateDeclarations.find(s => s.stateName === identifierName);
|
|
254
|
+
|
|
255
|
+
if (matchingState) {
|
|
256
|
+
jsxUsages.push({
|
|
257
|
+
stateName: identifierName,
|
|
258
|
+
location: {
|
|
259
|
+
line: loc?.start.line,
|
|
260
|
+
column: loc?.start.column,
|
|
261
|
+
},
|
|
262
|
+
usageType: 'expression',
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
|
|
268
|
+
// Also check JSX attributes
|
|
269
|
+
JSXAttribute(path) {
|
|
270
|
+
const { node } = path;
|
|
271
|
+
const loc = node.loc;
|
|
272
|
+
|
|
273
|
+
if (node.value?.type === 'JSXExpressionContainer') {
|
|
274
|
+
const expr = node.value.expression;
|
|
275
|
+
|
|
276
|
+
if (expr.type === 'Identifier') {
|
|
277
|
+
const matchingState = stateDeclarations.find(s => s.stateName === expr.name);
|
|
278
|
+
if (matchingState) {
|
|
279
|
+
jsxUsages.push({
|
|
280
|
+
stateName: expr.name,
|
|
281
|
+
location: {
|
|
282
|
+
line: loc?.start.line,
|
|
283
|
+
column: loc?.start.column,
|
|
284
|
+
},
|
|
285
|
+
usageType: `attribute:${node.name.name}`,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Also check member expressions: value={form.email}
|
|
291
|
+
if (expr.type === 'MemberExpression' && expr.object.type === 'Identifier') {
|
|
292
|
+
const matchingState = stateDeclarations.find(s => s.stateName === expr.object.name);
|
|
293
|
+
if (matchingState) {
|
|
294
|
+
jsxUsages.push({
|
|
295
|
+
stateName: expr.object.name,
|
|
296
|
+
location: {
|
|
297
|
+
line: loc?.start.line,
|
|
298
|
+
column: loc?.start.column,
|
|
299
|
+
},
|
|
300
|
+
usageType: `attribute:${node.name.name}`,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// Now emit promises only for states that are:
|
|
309
|
+
// 1. Declared with useState
|
|
310
|
+
// 2. Have setter calls
|
|
311
|
+
// 3. Are used in JSX
|
|
312
|
+
for (const stateDecl of stateDeclarations) {
|
|
313
|
+
const { stateName, setterName, componentName, location } = stateDecl;
|
|
314
|
+
|
|
315
|
+
const relatedSetterCalls = setterCalls.filter(c => c.stateName === stateName);
|
|
316
|
+
const relatedJSXUsages = jsxUsages.filter(u => u.stateName === stateName);
|
|
317
|
+
|
|
318
|
+
// PHASE 10: Only emit promise if:
|
|
319
|
+
// - State is used in JSX (proves UI connection)
|
|
320
|
+
// - Setter is called (proves mutation intent)
|
|
321
|
+
if (relatedJSXUsages.length > 0 && relatedSetterCalls.length > 0) {
|
|
322
|
+
// PHASE 10: Include context and AST source in promises
|
|
323
|
+
promises.push({
|
|
324
|
+
type: 'state-ui-promise',
|
|
325
|
+
stateName,
|
|
326
|
+
setterName,
|
|
327
|
+
componentName: componentName || 'UnknownComponent',
|
|
328
|
+
setterCallCount: relatedSetterCalls.length,
|
|
329
|
+
jsxUsageCount: relatedJSXUsages.length,
|
|
330
|
+
usageTypes: [...new Set(relatedJSXUsages.map(u => u.usageType))],
|
|
331
|
+
location,
|
|
332
|
+
// PHASE 10: Enhanced metadata with context and AST source
|
|
333
|
+
metadata: {
|
|
334
|
+
hasUpdaterFunction: relatedSetterCalls.some(c => c.isUpdaterFunction),
|
|
335
|
+
setterCalls: relatedSetterCalls.map(c => ({
|
|
336
|
+
context: c.context,
|
|
337
|
+
astSource: c.astSource,
|
|
338
|
+
isUIBound: c.isUIBound,
|
|
339
|
+
isUpdaterFunction: c.isUpdaterFunction,
|
|
340
|
+
location: c.location,
|
|
341
|
+
})),
|
|
342
|
+
},
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
} catch (error) {
|
|
348
|
+
// Parse errors are silently handled
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return promises;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Find the component name that contains this path
|
|
356
|
+
*/
|
|
357
|
+
function findComponentName(path) {
|
|
358
|
+
let current = path.parentPath;
|
|
359
|
+
|
|
360
|
+
while (current) {
|
|
361
|
+
const node = current.node;
|
|
362
|
+
|
|
363
|
+
// Function component
|
|
364
|
+
if (current.isFunctionDeclaration() && node.id?.name) {
|
|
365
|
+
return node.id.name;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Arrow function assigned to variable
|
|
369
|
+
if (current.isVariableDeclarator() && node.id?.name) {
|
|
370
|
+
return node.id.name;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Export default function
|
|
374
|
+
if (current.isExportDefaultDeclaration()) {
|
|
375
|
+
if (node.declaration?.id?.name) {
|
|
376
|
+
return node.declaration.id.name;
|
|
377
|
+
}
|
|
378
|
+
if (node.declaration?.type === 'FunctionExpression' ||
|
|
379
|
+
node.declaration?.type === 'ArrowFunctionExpression') {
|
|
380
|
+
return 'DefaultExport';
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
current = current.parentPath;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Infer the type of JSX usage
|
|
392
|
+
*/
|
|
393
|
+
function inferJSXUsageType(path) {
|
|
394
|
+
const parent = path.parent;
|
|
395
|
+
|
|
396
|
+
// Attribute usage
|
|
397
|
+
if (parent.type === 'JSXAttribute') {
|
|
398
|
+
return `attribute:${parent.name.name}`;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Text content
|
|
402
|
+
if (parent.type === 'JSXElement') {
|
|
403
|
+
return 'text-content';
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Conditional rendering
|
|
407
|
+
const expression = path.node.expression;
|
|
408
|
+
if (expression.type === 'ConditionalExpression' ||
|
|
409
|
+
expression.type === 'LogicalExpression') {
|
|
410
|
+
return 'conditional-rendering';
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return 'expression';
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Extract all identifiers from an expression
|
|
418
|
+
*/
|
|
419
|
+
function extractIdentifiers(node) {
|
|
420
|
+
const identifiers = [];
|
|
421
|
+
|
|
422
|
+
if (!node) return identifiers;
|
|
423
|
+
|
|
424
|
+
if (node.type === 'Identifier') {
|
|
425
|
+
identifiers.push(node.name);
|
|
426
|
+
} else if (node.type === 'ConditionalExpression') {
|
|
427
|
+
identifiers.push(...extractIdentifiers(node.test));
|
|
428
|
+
identifiers.push(...extractIdentifiers(node.consequent));
|
|
429
|
+
identifiers.push(...extractIdentifiers(node.alternate));
|
|
430
|
+
} else if (node.type === 'LogicalExpression') {
|
|
431
|
+
identifiers.push(...extractIdentifiers(node.left));
|
|
432
|
+
identifiers.push(...extractIdentifiers(node.right));
|
|
433
|
+
} else if (node.type === 'UnaryExpression') {
|
|
434
|
+
identifiers.push(...extractIdentifiers(node.argument));
|
|
435
|
+
} else if (node.type === 'BinaryExpression') {
|
|
436
|
+
identifiers.push(...extractIdentifiers(node.left));
|
|
437
|
+
identifiers.push(...extractIdentifiers(node.right));
|
|
438
|
+
} else if (node.type === 'MemberExpression') {
|
|
439
|
+
identifiers.push(...extractIdentifiers(node.object));
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return identifiers;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* PHASE 10: Infer execution context (handler, hook, component, etc.)
|
|
447
|
+
* Reuses Phase 9 style context tracking
|
|
448
|
+
*/
|
|
449
|
+
function inferContext(path) {
|
|
450
|
+
const contexts = [];
|
|
451
|
+
|
|
452
|
+
let current = path.parentPath;
|
|
453
|
+
while (current) {
|
|
454
|
+
const node = current.node;
|
|
455
|
+
|
|
456
|
+
// Event handler prop: onClick={() => ...}
|
|
457
|
+
if (current.isJSXAttribute()) {
|
|
458
|
+
const attrName = node.name.name;
|
|
459
|
+
if (attrName && attrName.startsWith('on')) {
|
|
460
|
+
contexts.push(`handler:${attrName}`);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Hook: useEffect(() => ...), useCallback(() => ...)
|
|
465
|
+
if (current.isCallExpression() &&
|
|
466
|
+
current.node.callee.type === 'Identifier') {
|
|
467
|
+
const calleeName = current.node.callee.name;
|
|
468
|
+
if (calleeName.startsWith('use')) {
|
|
469
|
+
contexts.push(`hook:${calleeName}`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Function/Arrow in component
|
|
474
|
+
if (current.isFunctionDeclaration() ||
|
|
475
|
+
current.isFunctionExpression() ||
|
|
476
|
+
current.isArrowFunctionExpression()) {
|
|
477
|
+
const funcName = getFunctionName(current);
|
|
478
|
+
if (funcName) {
|
|
479
|
+
// Check if it looks like a handler
|
|
480
|
+
if (funcName.startsWith('handle') || funcName.startsWith('on')) {
|
|
481
|
+
contexts.push(`handler:${funcName}`);
|
|
482
|
+
} else {
|
|
483
|
+
contexts.push(`function:${funcName}`);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
current = current.parentPath;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return contexts.length > 0 ? contexts.reverse().join(' > ') : 'top-level';
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* PHASE 10: Get function name from path
|
|
496
|
+
* Reuses Phase 9 helper
|
|
497
|
+
*/
|
|
498
|
+
function getFunctionName(path) {
|
|
499
|
+
const node = path.node;
|
|
500
|
+
|
|
501
|
+
// Named function
|
|
502
|
+
if (node.id?.name) {
|
|
503
|
+
return node.id.name;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Variable declarator: const handleClick = () => ...
|
|
507
|
+
const parent = path.parent;
|
|
508
|
+
if (parent.type === 'VariableDeclarator' && parent.id.name) {
|
|
509
|
+
return parent.id.name;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Object property: { onClick: () => ... }
|
|
513
|
+
if (parent.type === 'ObjectProperty' && parent.key.name) {
|
|
514
|
+
return parent.key.name;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* PHASE 10: Determine if handler is UI-bound (connected to user interaction)
|
|
522
|
+
* Reuses Phase 9 logic
|
|
523
|
+
*/
|
|
524
|
+
function isUIBoundHandler(path) {
|
|
525
|
+
const context = inferContext(path);
|
|
526
|
+
|
|
527
|
+
// Direct event handlers (onClick, onSubmit, etc.)
|
|
528
|
+
if (context.includes('handler:on')) {
|
|
529
|
+
return true;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Handler functions (handleClick, handleSubmit, etc.)
|
|
533
|
+
if (context.includes('handler:handle')) {
|
|
534
|
+
return true;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Check if function is referenced in JSX
|
|
538
|
+
let current = path.parentPath;
|
|
539
|
+
while (current) {
|
|
540
|
+
// Check if we're inside JSX attribute
|
|
541
|
+
if (current.isJSXAttribute()) {
|
|
542
|
+
const attrName = current.node.name?.name;
|
|
543
|
+
if (attrName && attrName.startsWith('on')) {
|
|
544
|
+
return true;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Check if function is assigned to event handler
|
|
549
|
+
if (current.isVariableDeclarator()) {
|
|
550
|
+
const varName = current.node.id?.name;
|
|
551
|
+
if (varName && (varName.startsWith('handle') || varName.startsWith('on'))) {
|
|
552
|
+
// Check if this variable is used in JSX
|
|
553
|
+
const binding = current.scope.getBinding(varName);
|
|
554
|
+
if (binding) {
|
|
555
|
+
for (const refPath of binding.referencePaths) {
|
|
556
|
+
if (refPath.findParent(p => p.isJSXAttribute())) {
|
|
557
|
+
return true;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
current = current.parentPath;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
return false;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* PHASE 10: Extract AST source code snippet for evidence
|
|
572
|
+
* Reuses Phase 9 helper
|
|
573
|
+
*/
|
|
574
|
+
function extractASTSource(node, lines, loc) {
|
|
575
|
+
if (!loc || !loc.start || !loc.end) {
|
|
576
|
+
return '';
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const startLine = loc.start.line - 1; // 0-indexed
|
|
580
|
+
const endLine = loc.end.line - 1;
|
|
581
|
+
|
|
582
|
+
if (startLine < 0 || endLine >= lines.length) {
|
|
583
|
+
return '';
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
if (startLine === endLine) {
|
|
587
|
+
// Single line - extract substring
|
|
588
|
+
const line = lines[startLine];
|
|
589
|
+
const startCol = loc.start.column;
|
|
590
|
+
const endCol = loc.end.column;
|
|
591
|
+
return line.substring(startCol, endCol).trim();
|
|
592
|
+
} else {
|
|
593
|
+
// Multi-line - extract full lines
|
|
594
|
+
const snippet = lines.slice(startLine, endLine + 1);
|
|
595
|
+
// Trim first line from start column, last line to end column
|
|
596
|
+
if (snippet.length > 0) {
|
|
597
|
+
snippet[0] = snippet[0].substring(loc.start.column);
|
|
598
|
+
snippet[snippet.length - 1] = snippet[snippet.length - 1].substring(0, loc.end.column);
|
|
599
|
+
}
|
|
600
|
+
return snippet.join('\n').trim();
|
|
601
|
+
}
|
|
602
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 21.6.1 — Bootstrap Guard
|
|
3
|
+
*
|
|
4
|
+
* Runtime assertions to prevent execution bootstrap during inspection commands.
|
|
5
|
+
* Hard crashes if forbidden operations are attempted.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Inspection command mode flag
|
|
10
|
+
*/
|
|
11
|
+
let isInspectionMode = false;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Enable inspection mode (called before inspection command dispatch)
|
|
15
|
+
*/
|
|
16
|
+
export function enableInspectionMode() {
|
|
17
|
+
isInspectionMode = true;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Disable inspection mode (called after inspection command completes)
|
|
22
|
+
*/
|
|
23
|
+
export function disableInspectionMode() {
|
|
24
|
+
isInspectionMode = false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if in inspection mode
|
|
29
|
+
*/
|
|
30
|
+
export function isInInspectionMode() {
|
|
31
|
+
return isInspectionMode;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Assert that execution bootstrap is allowed
|
|
36
|
+
* Throws if called during inspection mode
|
|
37
|
+
*/
|
|
38
|
+
export function assertExecutionBootstrapAllowed(operation) {
|
|
39
|
+
if (isInspectionMode) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`FORBIDDEN: ${operation} called during inspection command. ` +
|
|
42
|
+
`Inspection commands (ga, inspect, gates, doctor) must not trigger execution bootstrap.`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Guard wrapper for detectProject
|
|
49
|
+
*/
|
|
50
|
+
export function guardDetectProject(fn) {
|
|
51
|
+
return function(...args) {
|
|
52
|
+
assertExecutionBootstrapAllowed('detectProject');
|
|
53
|
+
return fn(...args);
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Guard wrapper for resolveURL
|
|
59
|
+
*/
|
|
60
|
+
export function guardResolveURL(fn) {
|
|
61
|
+
return function(...args) {
|
|
62
|
+
assertExecutionBootstrapAllowed('resolveURL');
|
|
63
|
+
return fn(...args);
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Guard wrapper for prompt
|
|
69
|
+
*/
|
|
70
|
+
export function guardPrompt(fn) {
|
|
71
|
+
return function(...args) {
|
|
72
|
+
assertExecutionBootstrapAllowed('prompt');
|
|
73
|
+
return fn(...args);
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Guard wrapper for browser setup
|
|
79
|
+
*/
|
|
80
|
+
export function guardBrowserSetup(fn) {
|
|
81
|
+
return function(...args) {
|
|
82
|
+
assertExecutionBootstrapAllowed('browser setup');
|
|
83
|
+
return fn(...args);
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* Produces exactly one finding per expectation with deterministic confidence and impact.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
export async function detectFindings(learnData, observeData, projectPath, onProgress) {
|
|
7
|
+
export async function detectFindings(learnData, observeData, projectPath, onProgress, options = {}) {
|
|
8
|
+
const log = options.silent ? () => {} : console.log;
|
|
8
9
|
const findings = [];
|
|
9
10
|
const stats = {
|
|
10
11
|
total: 0,
|
|
@@ -58,8 +59,8 @@ export async function detectFindings(learnData, observeData, projectPath, onProg
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
// Terminal narration (one line per finding + summary)
|
|
61
|
-
narration.forEach((line) =>
|
|
62
|
-
|
|
62
|
+
narration.forEach((line) => log(line));
|
|
63
|
+
log(`SUMMARY findings=${findings.length} observed=${stats.observed} silent-failure=${stats.silentFailures} coverage-gap=${stats.coverageGaps} unproven=${stats.unproven}`);
|
|
63
64
|
|
|
64
65
|
// Emit completion event
|
|
65
66
|
if (onProgress) {
|