@veraxhq/verax 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/README.md +14 -18
  2. package/bin/verax.js +7 -0
  3. package/package.json +3 -3
  4. package/src/cli/commands/baseline.js +104 -0
  5. package/src/cli/commands/default.js +79 -25
  6. package/src/cli/commands/ga.js +243 -0
  7. package/src/cli/commands/gates.js +95 -0
  8. package/src/cli/commands/inspect.js +131 -2
  9. package/src/cli/commands/release-check.js +213 -0
  10. package/src/cli/commands/run.js +246 -35
  11. package/src/cli/commands/security-check.js +211 -0
  12. package/src/cli/commands/truth.js +114 -0
  13. package/src/cli/entry.js +304 -67
  14. package/src/cli/util/angular-component-extractor.js +179 -0
  15. package/src/cli/util/angular-navigation-detector.js +141 -0
  16. package/src/cli/util/angular-network-detector.js +161 -0
  17. package/src/cli/util/angular-state-detector.js +162 -0
  18. package/src/cli/util/ast-interactive-detector.js +546 -0
  19. package/src/cli/util/ast-network-detector.js +603 -0
  20. package/src/cli/util/ast-usestate-detector.js +602 -0
  21. package/src/cli/util/bootstrap-guard.js +86 -0
  22. package/src/cli/util/determinism-runner.js +123 -0
  23. package/src/cli/util/determinism-writer.js +129 -0
  24. package/src/cli/util/env-url.js +4 -0
  25. package/src/cli/util/expectation-extractor.js +369 -73
  26. package/src/cli/util/findings-writer.js +126 -16
  27. package/src/cli/util/learn-writer.js +3 -1
  28. package/src/cli/util/observe-writer.js +3 -1
  29. package/src/cli/util/paths.js +3 -12
  30. package/src/cli/util/project-discovery.js +3 -0
  31. package/src/cli/util/project-writer.js +3 -1
  32. package/src/cli/util/run-resolver.js +64 -0
  33. package/src/cli/util/source-requirement.js +55 -0
  34. package/src/cli/util/summary-writer.js +1 -0
  35. package/src/cli/util/svelte-navigation-detector.js +163 -0
  36. package/src/cli/util/svelte-network-detector.js +80 -0
  37. package/src/cli/util/svelte-sfc-extractor.js +147 -0
  38. package/src/cli/util/svelte-state-detector.js +243 -0
  39. package/src/cli/util/vue-navigation-detector.js +177 -0
  40. package/src/cli/util/vue-sfc-extractor.js +162 -0
  41. package/src/cli/util/vue-state-detector.js +215 -0
  42. package/src/verax/cli/finding-explainer.js +56 -3
  43. package/src/verax/core/artifacts/registry.js +154 -0
  44. package/src/verax/core/artifacts/verifier.js +980 -0
  45. package/src/verax/core/baseline/baseline.enforcer.js +137 -0
  46. package/src/verax/core/baseline/baseline.snapshot.js +231 -0
  47. package/src/verax/core/capabilities/gates.js +499 -0
  48. package/src/verax/core/capabilities/registry.js +475 -0
  49. package/src/verax/core/confidence/confidence-compute.js +137 -0
  50. package/src/verax/core/confidence/confidence-invariants.js +234 -0
  51. package/src/verax/core/confidence/confidence-report-writer.js +112 -0
  52. package/src/verax/core/confidence/confidence-weights.js +44 -0
  53. package/src/verax/core/confidence/confidence.defaults.js +65 -0
  54. package/src/verax/core/confidence/confidence.loader.js +79 -0
  55. package/src/verax/core/confidence/confidence.schema.js +94 -0
  56. package/src/verax/core/confidence-engine-refactor.js +484 -0
  57. package/src/verax/core/confidence-engine.js +486 -0
  58. package/src/verax/core/confidence-engine.js.backup +471 -0
  59. package/src/verax/core/contracts/index.js +29 -0
  60. package/src/verax/core/contracts/types.js +185 -0
  61. package/src/verax/core/contracts/validators.js +381 -0
  62. package/src/verax/core/decision-snapshot.js +30 -3
  63. package/src/verax/core/decisions/decision.trace.js +276 -0
  64. package/src/verax/core/determinism/contract-writer.js +89 -0
  65. package/src/verax/core/determinism/contract.js +139 -0
  66. package/src/verax/core/determinism/diff.js +364 -0
  67. package/src/verax/core/determinism/engine.js +221 -0
  68. package/src/verax/core/determinism/finding-identity.js +148 -0
  69. package/src/verax/core/determinism/normalize.js +438 -0
  70. package/src/verax/core/determinism/report-writer.js +92 -0
  71. package/src/verax/core/determinism/run-fingerprint.js +118 -0
  72. package/src/verax/core/dynamic-route-intelligence.js +528 -0
  73. package/src/verax/core/evidence/evidence-capture-service.js +307 -0
  74. package/src/verax/core/evidence/evidence-intent-ledger.js +165 -0
  75. package/src/verax/core/evidence-builder.js +487 -0
  76. package/src/verax/core/execution-mode-context.js +77 -0
  77. package/src/verax/core/execution-mode-detector.js +190 -0
  78. package/src/verax/core/failures/exit-codes.js +86 -0
  79. package/src/verax/core/failures/failure-summary.js +76 -0
  80. package/src/verax/core/failures/failure.factory.js +225 -0
  81. package/src/verax/core/failures/failure.ledger.js +132 -0
  82. package/src/verax/core/failures/failure.types.js +196 -0
  83. package/src/verax/core/failures/index.js +10 -0
  84. package/src/verax/core/ga/ga-report-writer.js +43 -0
  85. package/src/verax/core/ga/ga.artifact.js +49 -0
  86. package/src/verax/core/ga/ga.contract.js +434 -0
  87. package/src/verax/core/ga/ga.enforcer.js +86 -0
  88. package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
  89. package/src/verax/core/guardrails/policy.defaults.js +210 -0
  90. package/src/verax/core/guardrails/policy.loader.js +83 -0
  91. package/src/verax/core/guardrails/policy.schema.js +110 -0
  92. package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
  93. package/src/verax/core/guardrails-engine.js +505 -0
  94. package/src/verax/core/observe/run-timeline.js +316 -0
  95. package/src/verax/core/perf/perf.contract.js +186 -0
  96. package/src/verax/core/perf/perf.display.js +65 -0
  97. package/src/verax/core/perf/perf.enforcer.js +91 -0
  98. package/src/verax/core/perf/perf.monitor.js +209 -0
  99. package/src/verax/core/perf/perf.report.js +198 -0
  100. package/src/verax/core/pipeline-tracker.js +238 -0
  101. package/src/verax/core/product-definition.js +127 -0
  102. package/src/verax/core/release/provenance.builder.js +271 -0
  103. package/src/verax/core/release/release-report-writer.js +40 -0
  104. package/src/verax/core/release/release.enforcer.js +159 -0
  105. package/src/verax/core/release/reproducibility.check.js +221 -0
  106. package/src/verax/core/release/sbom.builder.js +283 -0
  107. package/src/verax/core/report/cross-index.js +192 -0
  108. package/src/verax/core/report/human-summary.js +222 -0
  109. package/src/verax/core/route-intelligence.js +419 -0
  110. package/src/verax/core/security/secrets.scan.js +326 -0
  111. package/src/verax/core/security/security-report.js +50 -0
  112. package/src/verax/core/security/security.enforcer.js +124 -0
  113. package/src/verax/core/security/supplychain.defaults.json +38 -0
  114. package/src/verax/core/security/supplychain.policy.js +326 -0
  115. package/src/verax/core/security/vuln.scan.js +265 -0
  116. package/src/verax/core/truth/truth.certificate.js +250 -0
  117. package/src/verax/core/ui-feedback-intelligence.js +515 -0
  118. package/src/verax/detect/confidence-engine.js +628 -40
  119. package/src/verax/detect/confidence-helper.js +33 -0
  120. package/src/verax/detect/detection-engine.js +18 -1
  121. package/src/verax/detect/dynamic-route-findings.js +335 -0
  122. package/src/verax/detect/expectation-chain-detector.js +417 -0
  123. package/src/verax/detect/expectation-model.js +3 -1
  124. package/src/verax/detect/findings-writer.js +141 -5
  125. package/src/verax/detect/index.js +229 -5
  126. package/src/verax/detect/journey-stall-detector.js +558 -0
  127. package/src/verax/detect/route-findings.js +218 -0
  128. package/src/verax/detect/ui-feedback-findings.js +207 -0
  129. package/src/verax/detect/verdict-engine.js +57 -3
  130. package/src/verax/detect/view-switch-correlator.js +242 -0
  131. package/src/verax/index.js +413 -45
  132. package/src/verax/learn/action-contract-extractor.js +682 -64
  133. package/src/verax/learn/route-validator.js +4 -1
  134. package/src/verax/observe/index.js +88 -843
  135. package/src/verax/observe/interaction-runner.js +25 -8
  136. package/src/verax/observe/observe-context.js +205 -0
  137. package/src/verax/observe/observe-helpers.js +191 -0
  138. package/src/verax/observe/observe-runner.js +226 -0
  139. package/src/verax/observe/observers/budget-observer.js +185 -0
  140. package/src/verax/observe/observers/console-observer.js +102 -0
  141. package/src/verax/observe/observers/coverage-observer.js +107 -0
  142. package/src/verax/observe/observers/interaction-observer.js +471 -0
  143. package/src/verax/observe/observers/navigation-observer.js +132 -0
  144. package/src/verax/observe/observers/network-observer.js +87 -0
  145. package/src/verax/observe/observers/safety-observer.js +82 -0
  146. package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
  147. package/src/verax/observe/ui-feedback-detector.js +742 -0
  148. package/src/verax/observe/ui-signal-sensor.js +148 -2
  149. package/src/verax/scan-summary-writer.js +42 -8
  150. package/src/verax/shared/artifact-manager.js +8 -5
  151. package/src/verax/shared/css-spinner-rules.js +204 -0
  152. package/src/verax/shared/view-switch-rules.js +208 -0
@@ -0,0 +1,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
+