@veraxhq/verax 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) hide show
  1. package/README.md +14 -18
  2. package/bin/verax.js +7 -0
  3. package/package.json +15 -5
  4. package/src/cli/commands/baseline.js +104 -0
  5. package/src/cli/commands/default.js +323 -111
  6. package/src/cli/commands/doctor.js +36 -4
  7. package/src/cli/commands/ga.js +243 -0
  8. package/src/cli/commands/gates.js +95 -0
  9. package/src/cli/commands/inspect.js +131 -2
  10. package/src/cli/commands/release-check.js +213 -0
  11. package/src/cli/commands/run.js +498 -103
  12. package/src/cli/commands/security-check.js +211 -0
  13. package/src/cli/commands/truth.js +114 -0
  14. package/src/cli/entry.js +305 -68
  15. package/src/cli/util/angular-component-extractor.js +179 -0
  16. package/src/cli/util/angular-navigation-detector.js +141 -0
  17. package/src/cli/util/angular-network-detector.js +161 -0
  18. package/src/cli/util/angular-state-detector.js +162 -0
  19. package/src/cli/util/ast-interactive-detector.js +546 -0
  20. package/src/cli/util/ast-network-detector.js +603 -0
  21. package/src/cli/util/ast-usestate-detector.js +602 -0
  22. package/src/cli/util/bootstrap-guard.js +86 -0
  23. package/src/cli/util/detection-engine.js +4 -3
  24. package/src/cli/util/determinism-runner.js +123 -0
  25. package/src/cli/util/determinism-writer.js +129 -0
  26. package/src/cli/util/env-url.js +4 -0
  27. package/src/cli/util/events.js +76 -0
  28. package/src/cli/util/expectation-extractor.js +380 -74
  29. package/src/cli/util/findings-writer.js +126 -15
  30. package/src/cli/util/learn-writer.js +3 -1
  31. package/src/cli/util/observation-engine.js +69 -23
  32. package/src/cli/util/observe-writer.js +3 -1
  33. package/src/cli/util/paths.js +6 -14
  34. package/src/cli/util/project-discovery.js +23 -0
  35. package/src/cli/util/project-writer.js +3 -1
  36. package/src/cli/util/redact.js +2 -2
  37. package/src/cli/util/run-resolver.js +64 -0
  38. package/src/cli/util/runtime-budget.js +147 -0
  39. package/src/cli/util/source-requirement.js +55 -0
  40. package/src/cli/util/summary-writer.js +13 -1
  41. package/src/cli/util/svelte-navigation-detector.js +163 -0
  42. package/src/cli/util/svelte-network-detector.js +80 -0
  43. package/src/cli/util/svelte-sfc-extractor.js +147 -0
  44. package/src/cli/util/svelte-state-detector.js +243 -0
  45. package/src/cli/util/vue-navigation-detector.js +177 -0
  46. package/src/cli/util/vue-sfc-extractor.js +162 -0
  47. package/src/cli/util/vue-state-detector.js +215 -0
  48. package/src/types/global.d.ts +28 -0
  49. package/src/types/ts-ast.d.ts +24 -0
  50. package/src/verax/cli/doctor.js +2 -2
  51. package/src/verax/cli/finding-explainer.js +56 -3
  52. package/src/verax/cli/init.js +1 -1
  53. package/src/verax/cli/url-safety.js +12 -2
  54. package/src/verax/cli/wizard.js +13 -2
  55. package/src/verax/core/artifacts/registry.js +154 -0
  56. package/src/verax/core/artifacts/verifier.js +980 -0
  57. package/src/verax/core/baseline/baseline.enforcer.js +137 -0
  58. package/src/verax/core/baseline/baseline.snapshot.js +231 -0
  59. package/src/verax/core/budget-engine.js +1 -1
  60. package/src/verax/core/capabilities/gates.js +499 -0
  61. package/src/verax/core/capabilities/registry.js +475 -0
  62. package/src/verax/core/confidence/confidence-compute.js +137 -0
  63. package/src/verax/core/confidence/confidence-invariants.js +234 -0
  64. package/src/verax/core/confidence/confidence-report-writer.js +112 -0
  65. package/src/verax/core/confidence/confidence-weights.js +44 -0
  66. package/src/verax/core/confidence/confidence.defaults.js +65 -0
  67. package/src/verax/core/confidence/confidence.loader.js +79 -0
  68. package/src/verax/core/confidence/confidence.schema.js +94 -0
  69. package/src/verax/core/confidence-engine-refactor.js +484 -0
  70. package/src/verax/core/confidence-engine.js +486 -0
  71. package/src/verax/core/confidence-engine.js.backup +471 -0
  72. package/src/verax/core/contracts/index.js +29 -0
  73. package/src/verax/core/contracts/types.js +185 -0
  74. package/src/verax/core/contracts/validators.js +381 -0
  75. package/src/verax/core/decision-snapshot.js +31 -4
  76. package/src/verax/core/decisions/decision.trace.js +276 -0
  77. package/src/verax/core/determinism/contract-writer.js +89 -0
  78. package/src/verax/core/determinism/contract.js +139 -0
  79. package/src/verax/core/determinism/diff.js +364 -0
  80. package/src/verax/core/determinism/engine.js +221 -0
  81. package/src/verax/core/determinism/finding-identity.js +148 -0
  82. package/src/verax/core/determinism/normalize.js +438 -0
  83. package/src/verax/core/determinism/report-writer.js +92 -0
  84. package/src/verax/core/determinism/run-fingerprint.js +118 -0
  85. package/src/verax/core/determinism-model.js +35 -6
  86. package/src/verax/core/dynamic-route-intelligence.js +528 -0
  87. package/src/verax/core/evidence/evidence-capture-service.js +307 -0
  88. package/src/verax/core/evidence/evidence-intent-ledger.js +165 -0
  89. package/src/verax/core/evidence-builder.js +487 -0
  90. package/src/verax/core/execution-mode-context.js +77 -0
  91. package/src/verax/core/execution-mode-detector.js +190 -0
  92. package/src/verax/core/failures/exit-codes.js +86 -0
  93. package/src/verax/core/failures/failure-summary.js +76 -0
  94. package/src/verax/core/failures/failure.factory.js +225 -0
  95. package/src/verax/core/failures/failure.ledger.js +132 -0
  96. package/src/verax/core/failures/failure.types.js +196 -0
  97. package/src/verax/core/failures/index.js +10 -0
  98. package/src/verax/core/ga/ga-report-writer.js +43 -0
  99. package/src/verax/core/ga/ga.artifact.js +49 -0
  100. package/src/verax/core/ga/ga.contract.js +434 -0
  101. package/src/verax/core/ga/ga.enforcer.js +86 -0
  102. package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
  103. package/src/verax/core/guardrails/policy.defaults.js +210 -0
  104. package/src/verax/core/guardrails/policy.loader.js +83 -0
  105. package/src/verax/core/guardrails/policy.schema.js +110 -0
  106. package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
  107. package/src/verax/core/guardrails-engine.js +505 -0
  108. package/src/verax/core/incremental-store.js +15 -7
  109. package/src/verax/core/observe/run-timeline.js +316 -0
  110. package/src/verax/core/perf/perf.contract.js +186 -0
  111. package/src/verax/core/perf/perf.display.js +65 -0
  112. package/src/verax/core/perf/perf.enforcer.js +91 -0
  113. package/src/verax/core/perf/perf.monitor.js +209 -0
  114. package/src/verax/core/perf/perf.report.js +198 -0
  115. package/src/verax/core/pipeline-tracker.js +238 -0
  116. package/src/verax/core/product-definition.js +127 -0
  117. package/src/verax/core/release/provenance.builder.js +271 -0
  118. package/src/verax/core/release/release-report-writer.js +40 -0
  119. package/src/verax/core/release/release.enforcer.js +159 -0
  120. package/src/verax/core/release/reproducibility.check.js +221 -0
  121. package/src/verax/core/release/sbom.builder.js +283 -0
  122. package/src/verax/core/replay-validator.js +4 -4
  123. package/src/verax/core/replay.js +1 -1
  124. package/src/verax/core/report/cross-index.js +192 -0
  125. package/src/verax/core/report/human-summary.js +222 -0
  126. package/src/verax/core/route-intelligence.js +419 -0
  127. package/src/verax/core/security/secrets.scan.js +326 -0
  128. package/src/verax/core/security/security-report.js +50 -0
  129. package/src/verax/core/security/security.enforcer.js +124 -0
  130. package/src/verax/core/security/supplychain.defaults.json +38 -0
  131. package/src/verax/core/security/supplychain.policy.js +326 -0
  132. package/src/verax/core/security/vuln.scan.js +265 -0
  133. package/src/verax/core/silence-impact.js +1 -1
  134. package/src/verax/core/silence-model.js +9 -7
  135. package/src/verax/core/truth/truth.certificate.js +250 -0
  136. package/src/verax/core/ui-feedback-intelligence.js +515 -0
  137. package/src/verax/detect/comparison.js +8 -3
  138. package/src/verax/detect/confidence-engine.js +645 -57
  139. package/src/verax/detect/confidence-helper.js +33 -0
  140. package/src/verax/detect/detection-engine.js +19 -2
  141. package/src/verax/detect/dynamic-route-findings.js +335 -0
  142. package/src/verax/detect/evidence-index.js +15 -65
  143. package/src/verax/detect/expectation-chain-detector.js +417 -0
  144. package/src/verax/detect/expectation-model.js +56 -3
  145. package/src/verax/detect/explanation-helpers.js +1 -1
  146. package/src/verax/detect/finding-detector.js +2 -2
  147. package/src/verax/detect/findings-writer.js +149 -20
  148. package/src/verax/detect/flow-detector.js +4 -4
  149. package/src/verax/detect/index.js +265 -15
  150. package/src/verax/detect/interactive-findings.js +3 -4
  151. package/src/verax/detect/journey-stall-detector.js +558 -0
  152. package/src/verax/detect/route-findings.js +218 -0
  153. package/src/verax/detect/signal-mapper.js +2 -2
  154. package/src/verax/detect/skip-classifier.js +4 -4
  155. package/src/verax/detect/ui-feedback-findings.js +207 -0
  156. package/src/verax/detect/verdict-engine.js +61 -9
  157. package/src/verax/detect/view-switch-correlator.js +242 -0
  158. package/src/verax/flow/flow-engine.js +3 -2
  159. package/src/verax/flow/flow-spec.js +1 -2
  160. package/src/verax/index.js +413 -33
  161. package/src/verax/intel/effect-detector.js +1 -1
  162. package/src/verax/intel/index.js +2 -2
  163. package/src/verax/intel/route-extractor.js +3 -3
  164. package/src/verax/intel/vue-navigation-extractor.js +81 -18
  165. package/src/verax/intel/vue-router-extractor.js +4 -2
  166. package/src/verax/learn/action-contract-extractor.js +684 -66
  167. package/src/verax/learn/ast-contract-extractor.js +53 -1
  168. package/src/verax/learn/index.js +36 -2
  169. package/src/verax/learn/manifest-writer.js +28 -14
  170. package/src/verax/learn/route-extractor.js +1 -1
  171. package/src/verax/learn/route-validator.js +12 -8
  172. package/src/verax/learn/state-extractor.js +1 -1
  173. package/src/verax/learn/static-extractor-navigation.js +1 -1
  174. package/src/verax/learn/static-extractor-validation.js +2 -2
  175. package/src/verax/learn/static-extractor.js +8 -7
  176. package/src/verax/learn/ts-contract-resolver.js +14 -12
  177. package/src/verax/observe/browser.js +22 -3
  178. package/src/verax/observe/console-sensor.js +2 -2
  179. package/src/verax/observe/expectation-executor.js +2 -1
  180. package/src/verax/observe/focus-sensor.js +1 -1
  181. package/src/verax/observe/human-driver.js +29 -10
  182. package/src/verax/observe/index.js +92 -844
  183. package/src/verax/observe/interaction-discovery.js +27 -15
  184. package/src/verax/observe/interaction-runner.js +31 -14
  185. package/src/verax/observe/loading-sensor.js +6 -0
  186. package/src/verax/observe/navigation-sensor.js +1 -1
  187. package/src/verax/observe/observe-context.js +205 -0
  188. package/src/verax/observe/observe-helpers.js +191 -0
  189. package/src/verax/observe/observe-runner.js +226 -0
  190. package/src/verax/observe/observers/budget-observer.js +185 -0
  191. package/src/verax/observe/observers/console-observer.js +102 -0
  192. package/src/verax/observe/observers/coverage-observer.js +107 -0
  193. package/src/verax/observe/observers/interaction-observer.js +471 -0
  194. package/src/verax/observe/observers/navigation-observer.js +132 -0
  195. package/src/verax/observe/observers/network-observer.js +87 -0
  196. package/src/verax/observe/observers/safety-observer.js +82 -0
  197. package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
  198. package/src/verax/observe/settle.js +1 -0
  199. package/src/verax/observe/state-sensor.js +8 -4
  200. package/src/verax/observe/state-ui-sensor.js +7 -1
  201. package/src/verax/observe/traces-writer.js +27 -16
  202. package/src/verax/observe/ui-feedback-detector.js +742 -0
  203. package/src/verax/observe/ui-signal-sensor.js +155 -2
  204. package/src/verax/scan-summary-writer.js +46 -9
  205. package/src/verax/shared/artifact-manager.js +9 -6
  206. package/src/verax/shared/budget-profiles.js +2 -2
  207. package/src/verax/shared/caching.js +1 -1
  208. package/src/verax/shared/config-loader.js +1 -2
  209. package/src/verax/shared/css-spinner-rules.js +204 -0
  210. package/src/verax/shared/dynamic-route-utils.js +12 -6
  211. package/src/verax/shared/retry-policy.js +1 -6
  212. package/src/verax/shared/root-artifacts.js +1 -1
  213. package/src/verax/shared/view-switch-rules.js +208 -0
  214. package/src/verax/shared/zip-artifacts.js +1 -0
  215. package/src/verax/validate/context-validator.js +1 -1
  216. package/src/verax/observe/index.js.backup +0 -1
  217. package/src/verax/validate/context-validator.js.bak +0 -0
@@ -0,0 +1,141 @@
1
+ /**
2
+ * PHASE 20 — Angular Navigation Detector
3
+ *
4
+ * Detects navigation promises in Angular applications:
5
+ * - routerLink directive in templates
6
+ * - Router.navigate() calls in component methods
7
+ * - ActivatedRoute navigation
8
+ */
9
+
10
+ import { extractAngularComponent, extractTemplateBindings, mapTemplateHandlersToClass } from './angular-component-extractor.js';
11
+ import { parse } from '@babel/parser';
12
+ import traverse from '@babel/traverse';
13
+ import { readFileSync, existsSync } from 'fs';
14
+
15
+ /**
16
+ * Detect navigation promises in Angular component
17
+ *
18
+ * @param {string} filePath - Path to .ts file
19
+ * @param {string} content - Full file content
20
+ * @param {string} projectRoot - Project root directory
21
+ * @returns {Array} Array of navigation expectations
22
+ */
23
+ export function detectAngularNavigation(filePath, content, projectRoot) {
24
+ const expectations = [];
25
+
26
+ try {
27
+ const component = extractAngularComponent(content, filePath, projectRoot);
28
+
29
+ // Extract navigation from template (routerLink)
30
+ if (component.template) {
31
+ let templateContent = null;
32
+
33
+ if (component.template.isInline) {
34
+ templateContent = component.template.content;
35
+ } else if (existsSync(component.template.path)) {
36
+ templateContent = readFileSync(component.template.path, 'utf8');
37
+ }
38
+
39
+ if (templateContent) {
40
+ const templateBindings = extractTemplateBindings(templateContent);
41
+
42
+ // Extract routerLink directives
43
+ const routerLinkRegex = /routerLink\s*=\s*["']([^"']+)["']/g;
44
+ let linkMatch;
45
+ while ((linkMatch = routerLinkRegex.exec(templateContent)) !== null) {
46
+ const href = linkMatch[1];
47
+ // Skip external links and hash-only links
48
+ if (href.startsWith('http://') || href.startsWith('https://') || href.startsWith('#')) {
49
+ continue;
50
+ }
51
+
52
+ const beforeMatch = templateContent.substring(0, linkMatch.index);
53
+ const line = (beforeMatch.match(/\n/g) || []).length + 1;
54
+
55
+ expectations.push({
56
+ type: 'navigation',
57
+ target: href,
58
+ context: 'template',
59
+ sourceRef: {
60
+ file: component.template.isInline ? filePath : component.template.path,
61
+ line,
62
+ snippet: linkMatch[0],
63
+ },
64
+ proof: 'PROVEN_EXPECTATION',
65
+ metadata: {
66
+ navigationType: 'routerLink',
67
+ },
68
+ });
69
+ }
70
+ }
71
+ }
72
+
73
+ // Extract navigation from component class (Router.navigate())
74
+ if (component.componentClass && component.componentClass.content) {
75
+ try {
76
+ const ast = parse(component.componentClass.content, {
77
+ sourceType: 'module',
78
+ plugins: ['typescript', 'decorators-legacy', 'classProperties'],
79
+ });
80
+
81
+ traverse.default(ast, {
82
+ CallExpression(path) {
83
+ const { node } = path;
84
+
85
+ // Detect router.navigate() calls
86
+ if (
87
+ node.callee.type === 'MemberExpression' &&
88
+ node.callee.property.name === 'navigate' &&
89
+ node.arguments.length > 0
90
+ ) {
91
+ const arg = node.arguments[0];
92
+ let target = null;
93
+
94
+ if (arg.type === 'StringLiteral') {
95
+ target = arg.value;
96
+ } else if (arg.type === 'ArrayExpression' && arg.elements.length > 0) {
97
+ // Router.navigate(['/path']) or Router.navigate(['/path', param])
98
+ const firstElement = arg.elements[0];
99
+ if (firstElement.type === 'StringLiteral') {
100
+ target = firstElement.value;
101
+ }
102
+ } else if (arg.type === 'TemplateLiteral' && arg.quasis.length === 1) {
103
+ target = arg.quasis[0].value.raw;
104
+ }
105
+
106
+ if (target && !target.startsWith('http://') && !target.startsWith('https://') && !target.startsWith('#')) {
107
+ const location = node.loc;
108
+ const line = component.componentClass.startLine + (location ? location.start.line - 1 : 0);
109
+
110
+ expectations.push({
111
+ type: 'navigation',
112
+ target,
113
+ context: 'router-navigate',
114
+ sourceRef: {
115
+ file: filePath,
116
+ line,
117
+ snippet: component.componentClass.content.substring(
118
+ node.start - (ast.program.body[0]?.start || 0),
119
+ node.end - (ast.program.body[0]?.start || 0)
120
+ ),
121
+ },
122
+ proof: arg.type === 'StringLiteral' ? 'PROVEN_EXPECTATION' : 'LIKELY_EXPECTATION',
123
+ metadata: {
124
+ navigationType: 'router-navigate',
125
+ },
126
+ });
127
+ }
128
+ }
129
+ },
130
+ });
131
+ } catch (parseError) {
132
+ // Skip if parsing fails
133
+ }
134
+ }
135
+ } catch (error) {
136
+ // Skip if extraction fails
137
+ }
138
+
139
+ return expectations;
140
+ }
141
+
@@ -0,0 +1,161 @@
1
+ /**
2
+ * PHASE 20 — Angular Network Detector
3
+ *
4
+ * Detects network calls (HttpClient, fetch) in Angular component methods and services.
5
+ * Reuses AST network detector but ensures it works with Angular TypeScript files.
6
+ */
7
+
8
+ import { extractAngularComponent, extractTemplateBindings, mapTemplateHandlersToClass } from './angular-component-extractor.js';
9
+ import { detectNetworkCallsAST } from './ast-network-detector.js';
10
+ import { relative } from 'path';
11
+ import { readFileSync, existsSync } from 'fs';
12
+ import { parse } from '@babel/parser';
13
+ import traverse from '@babel/traverse';
14
+
15
+ /**
16
+ * Detect network promises in Angular component
17
+ *
18
+ * @param {string} filePath - Path to .ts file
19
+ * @param {string} content - Full file content
20
+ * @param {string} projectRoot - Project root directory
21
+ * @returns {Array} Array of network expectations
22
+ */
23
+ export function detectAngularNetwork(filePath, content, projectRoot) {
24
+ const expectations = [];
25
+
26
+ try {
27
+ const component = extractAngularComponent(content, filePath, projectRoot);
28
+
29
+ // Extract event handlers from template to identify UI-bound handlers
30
+ let templateBindings = null;
31
+ if (component.template) {
32
+ let templateContent = null;
33
+
34
+ if (component.template.isInline) {
35
+ templateContent = component.template.content;
36
+ } else if (existsSync(component.template.path)) {
37
+ templateContent = readFileSync(component.template.path, 'utf8');
38
+ }
39
+
40
+ if (templateContent) {
41
+ templateBindings = extractTemplateBindings(templateContent);
42
+ }
43
+ }
44
+
45
+ const mappedHandlers = component.componentClass && templateBindings
46
+ ? mapTemplateHandlersToClass(templateBindings.eventHandlers, component.componentClass.content)
47
+ : [];
48
+
49
+ const uiBoundHandlers = new Set(mappedHandlers.map(h => h.methodName));
50
+
51
+ // Process component class
52
+ if (component.componentClass && component.componentClass.content) {
53
+ // Use AST network detector on class content
54
+ const networkCalls = detectNetworkCallsAST(component.componentClass.content, filePath, relative(projectRoot, filePath));
55
+
56
+ // Also detect HttpClient calls specifically
57
+ const httpClientCalls = detectHttpClientCalls(component.componentClass.content, component.componentClass.startLine);
58
+
59
+ // Combine and filter network calls
60
+ const allNetworkCalls = [...networkCalls, ...httpClientCalls];
61
+
62
+ for (const networkCall of allNetworkCalls) {
63
+ // Check if this is in a UI-bound handler
64
+ const isUIBound = networkCall.context && uiBoundHandlers.has(networkCall.context);
65
+
66
+ // Skip analytics-only calls (filtered by guardrails later)
67
+ if (networkCall.target && (
68
+ networkCall.target.includes('/api/analytics') ||
69
+ networkCall.target.includes('/api/track') ||
70
+ networkCall.target.includes('/api/beacon')
71
+ )) {
72
+ continue;
73
+ }
74
+
75
+ expectations.push({
76
+ type: 'network',
77
+ target: networkCall.target || networkCall.url,
78
+ method: networkCall.method || 'GET',
79
+ context: networkCall.context || 'component',
80
+ sourceRef: {
81
+ file: filePath,
82
+ line: networkCall.line || component.componentClass.startLine,
83
+ snippet: networkCall.snippet || '',
84
+ },
85
+ proof: networkCall.proof || 'LIKELY_EXPECTATION',
86
+ metadata: {
87
+ isUIBound,
88
+ handlerContext: networkCall.context,
89
+ networkKind: networkCall.kind || 'http',
90
+ },
91
+ });
92
+ }
93
+ }
94
+ } catch (error) {
95
+ // Skip if extraction fails
96
+ }
97
+
98
+ return expectations;
99
+ }
100
+
101
+ /**
102
+ * Detect HttpClient calls (get, post, put, delete, etc.)
103
+ *
104
+ * @param {string} classContent - Component class content
105
+ * @param {number} startLine - Starting line number
106
+ * @returns {Array} Array of network call detections
107
+ */
108
+ function detectHttpClientCalls(classContent, startLine) {
109
+ const calls = [];
110
+
111
+ try {
112
+ const ast = parse(classContent, {
113
+ sourceType: 'module',
114
+ plugins: ['typescript', 'decorators-legacy', 'classProperties'],
115
+ });
116
+
117
+ traverse.default(ast, {
118
+ CallExpression(path) {
119
+ const { node } = path;
120
+
121
+ // Detect http.get(), http.post(), etc.
122
+ if (
123
+ node.callee.type === 'MemberExpression' &&
124
+ ['get', 'post', 'put', 'delete', 'patch'].includes(node.callee.property.name)
125
+ ) {
126
+ const method = node.callee.property.name.toUpperCase();
127
+ const firstArg = node.arguments[0];
128
+ let url = null;
129
+
130
+ if (firstArg && firstArg.type === 'StringLiteral') {
131
+ url = firstArg.value;
132
+ } else if (firstArg && firstArg.type === 'TemplateLiteral' && firstArg.quasis.length === 1) {
133
+ url = firstArg.quasis[0].value.raw;
134
+ }
135
+
136
+ if (url) {
137
+ const location = node.loc;
138
+ const line = startLine + (location ? location.start.line - 1 : 0);
139
+
140
+ calls.push({
141
+ target: url,
142
+ method,
143
+ line,
144
+ snippet: classContent.substring(
145
+ node.start - (ast.program.body[0]?.start || 0),
146
+ node.end - (ast.program.body[0]?.start || 0)
147
+ ),
148
+ kind: 'httpClient',
149
+ proof: firstArg.type === 'StringLiteral' ? 'PROVEN_EXPECTATION' : 'LIKELY_EXPECTATION',
150
+ });
151
+ }
152
+ }
153
+ },
154
+ });
155
+ } catch (parseError) {
156
+ // Skip if parsing fails
157
+ }
158
+
159
+ return calls;
160
+ }
161
+
@@ -0,0 +1,162 @@
1
+ /**
2
+ * PHASE 20 — Angular State Detector
3
+ *
4
+ * Detects state mutations (component properties, services) in Angular components.
5
+ * Only emits state promises if state is user-visible (used in template bindings).
6
+ */
7
+
8
+ import { extractAngularComponent, extractTemplateBindings } from './angular-component-extractor.js';
9
+ import { parse } from '@babel/parser';
10
+ import traverse from '@babel/traverse';
11
+ import { readFileSync, existsSync } from 'fs';
12
+
13
+ /**
14
+ * Detect state promises in Angular component
15
+ *
16
+ * @param {string} filePath - Path to .ts file
17
+ * @param {string} content - Full file content
18
+ * @param {string} projectRoot - Project root directory
19
+ * @returns {Array} Array of state expectations
20
+ */
21
+ export function detectAngularState(filePath, content, projectRoot) {
22
+ const expectations = [];
23
+
24
+ try {
25
+ const component = extractAngularComponent(content, filePath, projectRoot);
26
+
27
+ // Extract template bindings to identify user-visible state
28
+ let templateBindings = null;
29
+ let templateContent = null;
30
+
31
+ if (component.template) {
32
+ if (component.template.isInline) {
33
+ templateContent = component.template.content;
34
+ } else if (existsSync(component.template.path)) {
35
+ templateContent = readFileSync(component.template.path, 'utf8');
36
+ }
37
+
38
+ if (templateContent) {
39
+ templateBindings = extractTemplateBindings(templateContent);
40
+ }
41
+ }
42
+
43
+ // Collect all state variables used in template
44
+ const templateStateVars = new Set();
45
+
46
+ // From property bindings: [property]="value"
47
+ if (templateBindings) {
48
+ templateBindings.propertyBindings.forEach(binding => {
49
+ templateStateVars.add(binding.value);
50
+ });
51
+
52
+ // From structural directives: *ngIf="condition"
53
+ templateBindings.structuralDirectives.forEach(directive => {
54
+ // Extract variable names from expressions
55
+ const varMatch = directive.expression.match(/^([a-zA-Z_$][a-zA-Z0-9_$]*)/);
56
+ if (varMatch) {
57
+ templateStateVars.add(varMatch[1]);
58
+ }
59
+ });
60
+ }
61
+
62
+ // From template interpolation: {{ variable }}
63
+ if (templateContent) {
64
+ const interpolationRegex = /\{\{\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\}\}/g;
65
+ let varMatch;
66
+ while ((varMatch = interpolationRegex.exec(templateContent)) !== null) {
67
+ templateStateVars.add(varMatch[1]);
68
+ }
69
+ }
70
+
71
+ // Process component class to find state mutations
72
+ if (component.componentClass && component.componentClass.content) {
73
+ try {
74
+ const ast = parse(component.componentClass.content, {
75
+ sourceType: 'module',
76
+ plugins: ['typescript', 'decorators-legacy', 'classProperties'],
77
+ });
78
+
79
+ traverse.default(ast, {
80
+ // Detect property assignments: this.property = value
81
+ AssignmentExpression(path) {
82
+ const { node } = path;
83
+
84
+ if (
85
+ node.left.type === 'MemberExpression' &&
86
+ node.left.object.type === 'ThisExpression' &&
87
+ node.left.property.type === 'Identifier'
88
+ ) {
89
+ const propertyName = node.left.property.name;
90
+
91
+ // Only emit if property is used in template
92
+ if (templateStateVars.has(propertyName)) {
93
+ const location = node.loc;
94
+ const line = component.componentClass.startLine + (location ? location.start.line - 1 : 0);
95
+
96
+ expectations.push({
97
+ type: 'state',
98
+ expectedTarget: propertyName,
99
+ context: 'property-assignment',
100
+ sourceRef: {
101
+ file: filePath,
102
+ line,
103
+ snippet: component.componentClass.content.substring(
104
+ node.start - (ast.program.body[0]?.start || 0),
105
+ node.end - (ast.program.body[0]?.start || 0)
106
+ ),
107
+ },
108
+ proof: 'PROVEN_EXPECTATION',
109
+ metadata: {
110
+ templateUsage: Array.from(templateStateVars).filter(v => v === propertyName).length,
111
+ stateType: 'component-property',
112
+ },
113
+ });
114
+ }
115
+ }
116
+ },
117
+
118
+ // Detect property declarations: property: type = value
119
+ ClassProperty(path) {
120
+ const { node } = path;
121
+
122
+ if (node.key.type === 'Identifier') {
123
+ const propertyName = node.key.name;
124
+
125
+ // Only emit if property is used in template and has initializer
126
+ if (templateStateVars.has(propertyName) && node.value) {
127
+ const location = node.loc;
128
+ const line = component.componentClass.startLine + (location ? location.start.line - 1 : 0);
129
+
130
+ expectations.push({
131
+ type: 'state',
132
+ expectedTarget: propertyName,
133
+ context: 'property-declaration',
134
+ sourceRef: {
135
+ file: filePath,
136
+ line,
137
+ snippet: component.componentClass.content.substring(
138
+ node.start - (ast.program.body[0]?.start || 0),
139
+ node.end - (ast.program.body[0]?.start || 0)
140
+ ),
141
+ },
142
+ proof: 'PROVEN_EXPECTATION',
143
+ metadata: {
144
+ templateUsage: Array.from(templateStateVars).filter(v => v === propertyName).length,
145
+ stateType: 'component-property',
146
+ },
147
+ });
148
+ }
149
+ }
150
+ },
151
+ });
152
+ } catch (parseError) {
153
+ // Skip if parsing fails
154
+ }
155
+ }
156
+ } catch (error) {
157
+ // Skip if extraction fails
158
+ }
159
+
160
+ return expectations;
161
+ }
162
+