api-tests-coverage 1.0.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 (288) hide show
  1. package/README.md +703 -0
  2. package/config.yaml.example +227 -0
  3. package/dist/action/src/index.d.ts +2 -0
  4. package/dist/action/src/index.d.ts.map +1 -0
  5. package/dist/action/src/index.js +349 -0
  6. package/dist/action/src/prComment.d.ts +34 -0
  7. package/dist/action/src/prComment.d.ts.map +1 -0
  8. package/dist/action/src/prComment.js +146 -0
  9. package/dist/src/ast/astAnalysisOrchestrator.d.ts +36 -0
  10. package/dist/src/ast/astAnalysisOrchestrator.d.ts.map +1 -0
  11. package/dist/src/ast/astAnalysisOrchestrator.js +123 -0
  12. package/dist/src/ast/astTypes.d.ts +105 -0
  13. package/dist/src/ast/astTypes.d.ts.map +1 -0
  14. package/dist/src/ast/astTypes.js +9 -0
  15. package/dist/src/ast/languageAnalyzer.d.ts +46 -0
  16. package/dist/src/ast/languageAnalyzer.d.ts.map +1 -0
  17. package/dist/src/ast/languageAnalyzer.js +9 -0
  18. package/dist/src/ast/languageCapabilities.d.ts +24 -0
  19. package/dist/src/ast/languageCapabilities.d.ts.map +1 -0
  20. package/dist/src/ast/languageCapabilities.js +92 -0
  21. package/dist/src/ast/parseFile.d.ts +16 -0
  22. package/dist/src/ast/parseFile.d.ts.map +1 -0
  23. package/dist/src/ast/parseFile.js +65 -0
  24. package/dist/src/ast/parserRegistry.d.ts +39 -0
  25. package/dist/src/ast/parserRegistry.d.ts.map +1 -0
  26. package/dist/src/ast/parserRegistry.js +66 -0
  27. package/dist/src/buildSummary.d.ts +26 -0
  28. package/dist/src/buildSummary.d.ts.map +1 -0
  29. package/dist/src/buildSummary.js +193 -0
  30. package/dist/src/businessCoverage.d.ts +68 -0
  31. package/dist/src/businessCoverage.d.ts.map +1 -0
  32. package/dist/src/businessCoverage.js +290 -0
  33. package/dist/src/compatibilityCoverage.d.ts +83 -0
  34. package/dist/src/compatibilityCoverage.d.ts.map +1 -0
  35. package/dist/src/compatibilityCoverage.js +501 -0
  36. package/dist/src/config/defaultConfig.d.ts +9 -0
  37. package/dist/src/config/defaultConfig.d.ts.map +1 -0
  38. package/dist/src/config/defaultConfig.js +97 -0
  39. package/dist/src/config/index.d.ts +12 -0
  40. package/dist/src/config/index.d.ts.map +1 -0
  41. package/dist/src/config/index.js +37 -0
  42. package/dist/src/config/loadConfig.d.ts +29 -0
  43. package/dist/src/config/loadConfig.d.ts.map +1 -0
  44. package/dist/src/config/loadConfig.js +135 -0
  45. package/dist/src/config/mergeConfig.d.ts +15 -0
  46. package/dist/src/config/mergeConfig.d.ts.map +1 -0
  47. package/dist/src/config/mergeConfig.js +57 -0
  48. package/dist/src/config/schema.d.ts +15 -0
  49. package/dist/src/config/schema.d.ts.map +1 -0
  50. package/dist/src/config/schema.js +30 -0
  51. package/dist/src/config/types.d.ts +175 -0
  52. package/dist/src/config/types.d.ts.map +1 -0
  53. package/dist/src/config/types.js +9 -0
  54. package/dist/src/config/validateConfig.d.ts +22 -0
  55. package/dist/src/config/validateConfig.d.ts.map +1 -0
  56. package/dist/src/config/validateConfig.js +171 -0
  57. package/dist/src/config.d.ts +168 -0
  58. package/dist/src/config.d.ts.map +1 -0
  59. package/dist/src/config.js +204 -0
  60. package/dist/src/coverage/deep-analysis/callGraph.d.ts +67 -0
  61. package/dist/src/coverage/deep-analysis/callGraph.d.ts.map +1 -0
  62. package/dist/src/coverage/deep-analysis/callGraph.js +275 -0
  63. package/dist/src/coverage/deep-analysis/deepEndpointResolver.d.ts +23 -0
  64. package/dist/src/coverage/deep-analysis/deepEndpointResolver.d.ts.map +1 -0
  65. package/dist/src/coverage/deep-analysis/deepEndpointResolver.js +394 -0
  66. package/dist/src/coverage/deep-analysis/index.d.ts +17 -0
  67. package/dist/src/coverage/deep-analysis/index.d.ts.map +1 -0
  68. package/dist/src/coverage/deep-analysis/index.js +63 -0
  69. package/dist/src/coverage/deep-analysis/resolveAssertions.d.ts +60 -0
  70. package/dist/src/coverage/deep-analysis/resolveAssertions.d.ts.map +1 -0
  71. package/dist/src/coverage/deep-analysis/resolveAssertions.js +121 -0
  72. package/dist/src/coverage/deep-analysis/resolveConstants.d.ts +36 -0
  73. package/dist/src/coverage/deep-analysis/resolveConstants.d.ts.map +1 -0
  74. package/dist/src/coverage/deep-analysis/resolveConstants.js +92 -0
  75. package/dist/src/coverage/deep-analysis/resolveEnums.d.ts +55 -0
  76. package/dist/src/coverage/deep-analysis/resolveEnums.d.ts.map +1 -0
  77. package/dist/src/coverage/deep-analysis/resolveEnums.js +152 -0
  78. package/dist/src/coverage/deep-analysis/resolveMethodChains.d.ts +70 -0
  79. package/dist/src/coverage/deep-analysis/resolveMethodChains.d.ts.map +1 -0
  80. package/dist/src/coverage/deep-analysis/resolveMethodChains.js +152 -0
  81. package/dist/src/coverage/deep-analysis/resolvePaths.d.ts +80 -0
  82. package/dist/src/coverage/deep-analysis/resolvePaths.d.ts.map +1 -0
  83. package/dist/src/coverage/deep-analysis/resolvePaths.js +216 -0
  84. package/dist/src/coverage/deep-analysis/resolveRequestWrappers.d.ts +71 -0
  85. package/dist/src/coverage/deep-analysis/resolveRequestWrappers.d.ts.map +1 -0
  86. package/dist/src/coverage/deep-analysis/resolveRequestWrappers.js +226 -0
  87. package/dist/src/coverage/deep-analysis/symbolTable.d.ts +58 -0
  88. package/dist/src/coverage/deep-analysis/symbolTable.d.ts.map +1 -0
  89. package/dist/src/coverage/deep-analysis/symbolTable.js +230 -0
  90. package/dist/src/coverage/deep-analysis/types.d.ts +122 -0
  91. package/dist/src/coverage/deep-analysis/types.d.ts.map +1 -0
  92. package/dist/src/coverage/deep-analysis/types.js +21 -0
  93. package/dist/src/discovery/fileClassifier.d.ts +50 -0
  94. package/dist/src/discovery/fileClassifier.d.ts.map +1 -0
  95. package/dist/src/discovery/fileClassifier.js +238 -0
  96. package/dist/src/discovery/projectDiscovery.d.ts +66 -0
  97. package/dist/src/discovery/projectDiscovery.d.ts.map +1 -0
  98. package/dist/src/discovery/projectDiscovery.js +287 -0
  99. package/dist/src/endpointCoverage.d.ts +70 -0
  100. package/dist/src/endpointCoverage.d.ts.map +1 -0
  101. package/dist/src/endpointCoverage.js +381 -0
  102. package/dist/src/errorCoverage.d.ts +93 -0
  103. package/dist/src/errorCoverage.d.ts.map +1 -0
  104. package/dist/src/errorCoverage.js +698 -0
  105. package/dist/src/index.d.ts +3 -0
  106. package/dist/src/index.d.ts.map +1 -0
  107. package/dist/src/index.js +1441 -0
  108. package/dist/src/inference/businessRuleInference.d.ts +63 -0
  109. package/dist/src/inference/businessRuleInference.d.ts.map +1 -0
  110. package/dist/src/inference/businessRuleInference.js +268 -0
  111. package/dist/src/inference/integrationFlowInference.d.ts +56 -0
  112. package/dist/src/inference/integrationFlowInference.d.ts.map +1 -0
  113. package/dist/src/inference/integrationFlowInference.js +266 -0
  114. package/dist/src/integrationCoverage.d.ts +72 -0
  115. package/dist/src/integrationCoverage.d.ts.map +1 -0
  116. package/dist/src/integrationCoverage.js +317 -0
  117. package/dist/src/intelligence/index.d.ts +20 -0
  118. package/dist/src/intelligence/index.d.ts.map +1 -0
  119. package/dist/src/intelligence/index.js +105 -0
  120. package/dist/src/intelligence/linkageEngine.d.ts +20 -0
  121. package/dist/src/intelligence/linkageEngine.d.ts.map +1 -0
  122. package/dist/src/intelligence/linkageEngine.js +522 -0
  123. package/dist/src/intelligence/markdownReporter.d.ts +12 -0
  124. package/dist/src/intelligence/markdownReporter.d.ts.map +1 -0
  125. package/dist/src/intelligence/markdownReporter.js +265 -0
  126. package/dist/src/intelligence/riskScoring.d.ts +53 -0
  127. package/dist/src/intelligence/riskScoring.d.ts.map +1 -0
  128. package/dist/src/intelligence/riskScoring.js +181 -0
  129. package/dist/src/intelligence/types.d.ts +121 -0
  130. package/dist/src/intelligence/types.d.ts.map +1 -0
  131. package/dist/src/intelligence/types.js +8 -0
  132. package/dist/src/languageDetection.d.ts +100 -0
  133. package/dist/src/languageDetection.d.ts.map +1 -0
  134. package/dist/src/languageDetection.js +349 -0
  135. package/dist/src/languages/java/index.d.ts +16 -0
  136. package/dist/src/languages/java/index.d.ts.map +1 -0
  137. package/dist/src/languages/java/index.js +103 -0
  138. package/dist/src/languages/java/parser.d.ts +7 -0
  139. package/dist/src/languages/java/parser.d.ts.map +1 -0
  140. package/dist/src/languages/java/parser.js +50 -0
  141. package/dist/src/languages/java/semanticBuilder.d.ts +21 -0
  142. package/dist/src/languages/java/semanticBuilder.d.ts.map +1 -0
  143. package/dist/src/languages/java/semanticBuilder.js +358 -0
  144. package/dist/src/languages/javascript/annotationExtractor.d.ts +20 -0
  145. package/dist/src/languages/javascript/annotationExtractor.d.ts.map +1 -0
  146. package/dist/src/languages/javascript/annotationExtractor.js +94 -0
  147. package/dist/src/languages/javascript/assertionResolver.d.ts +18 -0
  148. package/dist/src/languages/javascript/assertionResolver.d.ts.map +1 -0
  149. package/dist/src/languages/javascript/assertionResolver.js +150 -0
  150. package/dist/src/languages/javascript/callResolver.d.ts +23 -0
  151. package/dist/src/languages/javascript/callResolver.d.ts.map +1 -0
  152. package/dist/src/languages/javascript/callResolver.js +236 -0
  153. package/dist/src/languages/javascript/httpInteractionExtractor.d.ts +23 -0
  154. package/dist/src/languages/javascript/httpInteractionExtractor.d.ts.map +1 -0
  155. package/dist/src/languages/javascript/httpInteractionExtractor.js +205 -0
  156. package/dist/src/languages/javascript/index.d.ts +20 -0
  157. package/dist/src/languages/javascript/index.d.ts.map +1 -0
  158. package/dist/src/languages/javascript/index.js +136 -0
  159. package/dist/src/languages/javascript/parser.d.ts +14 -0
  160. package/dist/src/languages/javascript/parser.d.ts.map +1 -0
  161. package/dist/src/languages/javascript/parser.js +38 -0
  162. package/dist/src/languages/javascript/symbolResolver.d.ts +31 -0
  163. package/dist/src/languages/javascript/symbolResolver.d.ts.map +1 -0
  164. package/dist/src/languages/javascript/symbolResolver.js +183 -0
  165. package/dist/src/languages/kotlin/index.d.ts +16 -0
  166. package/dist/src/languages/kotlin/index.d.ts.map +1 -0
  167. package/dist/src/languages/kotlin/index.js +151 -0
  168. package/dist/src/languages/kotlin/parser.d.ts +11 -0
  169. package/dist/src/languages/kotlin/parser.d.ts.map +1 -0
  170. package/dist/src/languages/kotlin/parser.js +74 -0
  171. package/dist/src/languages/python/index.d.ts +15 -0
  172. package/dist/src/languages/python/index.d.ts.map +1 -0
  173. package/dist/src/languages/python/index.js +293 -0
  174. package/dist/src/languages/ruby/index.d.ts +15 -0
  175. package/dist/src/languages/ruby/index.d.ts.map +1 -0
  176. package/dist/src/languages/ruby/index.js +274 -0
  177. package/dist/src/languages/shared/treeSitterUtils.d.ts +43 -0
  178. package/dist/src/languages/shared/treeSitterUtils.d.ts.map +1 -0
  179. package/dist/src/languages/shared/treeSitterUtils.js +100 -0
  180. package/dist/src/languages/typescript/index.d.ts +14 -0
  181. package/dist/src/languages/typescript/index.d.ts.map +1 -0
  182. package/dist/src/languages/typescript/index.js +25 -0
  183. package/dist/src/lib/index.d.ts +228 -0
  184. package/dist/src/lib/index.d.ts.map +1 -0
  185. package/dist/src/lib/index.js +486 -0
  186. package/dist/src/mcp/client/index.d.ts +37 -0
  187. package/dist/src/mcp/client/index.d.ts.map +1 -0
  188. package/dist/src/mcp/client/index.js +235 -0
  189. package/dist/src/mcp/config.d.ts +50 -0
  190. package/dist/src/mcp/config.d.ts.map +1 -0
  191. package/dist/src/mcp/config.js +125 -0
  192. package/dist/src/mcp/events.d.ts +24 -0
  193. package/dist/src/mcp/events.d.ts.map +1 -0
  194. package/dist/src/mcp/events.js +48 -0
  195. package/dist/src/mcp/fallback/index.d.ts +50 -0
  196. package/dist/src/mcp/fallback/index.d.ts.map +1 -0
  197. package/dist/src/mcp/fallback/index.js +216 -0
  198. package/dist/src/mcp/index.d.ts +67 -0
  199. package/dist/src/mcp/index.d.ts.map +1 -0
  200. package/dist/src/mcp/index.js +212 -0
  201. package/dist/src/mcp/normalizer.d.ts +21 -0
  202. package/dist/src/mcp/normalizer.d.ts.map +1 -0
  203. package/dist/src/mcp/normalizer.js +99 -0
  204. package/dist/src/mcp/prompts/index.d.ts +86 -0
  205. package/dist/src/mcp/prompts/index.d.ts.map +1 -0
  206. package/dist/src/mcp/prompts/index.js +304 -0
  207. package/dist/src/mcp/templates/index.d.ts +35 -0
  208. package/dist/src/mcp/templates/index.d.ts.map +1 -0
  209. package/dist/src/mcp/templates/index.js +143 -0
  210. package/dist/src/mcp/testing/mock-server/index.d.ts +47 -0
  211. package/dist/src/mcp/testing/mock-server/index.d.ts.map +1 -0
  212. package/dist/src/mcp/testing/mock-server/index.js +157 -0
  213. package/dist/src/mcp/types.d.ts +127 -0
  214. package/dist/src/mcp/types.d.ts.map +1 -0
  215. package/dist/src/mcp/types.js +8 -0
  216. package/dist/src/observability.d.ts +138 -0
  217. package/dist/src/observability.d.ts.map +1 -0
  218. package/dist/src/observability.js +519 -0
  219. package/dist/src/parameterCoverage.d.ts +75 -0
  220. package/dist/src/parameterCoverage.d.ts.map +1 -0
  221. package/dist/src/parameterCoverage.js +629 -0
  222. package/dist/src/perfResilienceCoverage.d.ts +155 -0
  223. package/dist/src/perfResilienceCoverage.d.ts.map +1 -0
  224. package/dist/src/perfResilienceCoverage.js +670 -0
  225. package/dist/src/pluginLoader.d.ts +51 -0
  226. package/dist/src/pluginLoader.d.ts.map +1 -0
  227. package/dist/src/pluginLoader.js +72 -0
  228. package/dist/src/publishing.d.ts +63 -0
  229. package/dist/src/publishing.d.ts.map +1 -0
  230. package/dist/src/publishing.js +379 -0
  231. package/dist/src/qualityGate.d.ts +58 -0
  232. package/dist/src/qualityGate.d.ts.map +1 -0
  233. package/dist/src/qualityGate.js +118 -0
  234. package/dist/src/reporting.d.ts +41 -0
  235. package/dist/src/reporting.d.ts.map +1 -0
  236. package/dist/src/reporting.js +278 -0
  237. package/dist/src/screenshots.d.ts +71 -0
  238. package/dist/src/screenshots.d.ts.map +1 -0
  239. package/dist/src/screenshots.js +141 -0
  240. package/dist/src/security/gate/index.d.ts +11 -0
  241. package/dist/src/security/gate/index.d.ts.map +1 -0
  242. package/dist/src/security/gate/index.js +65 -0
  243. package/dist/src/security/index.d.ts +30 -0
  244. package/dist/src/security/index.d.ts.map +1 -0
  245. package/dist/src/security/index.js +342 -0
  246. package/dist/src/security/normalizers/semgrep.d.ts +10 -0
  247. package/dist/src/security/normalizers/semgrep.d.ts.map +1 -0
  248. package/dist/src/security/normalizers/semgrep.js +104 -0
  249. package/dist/src/security/normalizers/trivy.d.ts +10 -0
  250. package/dist/src/security/normalizers/trivy.d.ts.map +1 -0
  251. package/dist/src/security/normalizers/trivy.js +78 -0
  252. package/dist/src/security/normalizers/zap.d.ts +10 -0
  253. package/dist/src/security/normalizers/zap.d.ts.map +1 -0
  254. package/dist/src/security/normalizers/zap.js +104 -0
  255. package/dist/src/security/scanners/semgrep.d.ts +6 -0
  256. package/dist/src/security/scanners/semgrep.d.ts.map +1 -0
  257. package/dist/src/security/scanners/semgrep.js +125 -0
  258. package/dist/src/security/scanners/trivy.d.ts +6 -0
  259. package/dist/src/security/scanners/trivy.d.ts.map +1 -0
  260. package/dist/src/security/scanners/trivy.js +115 -0
  261. package/dist/src/security/scanners/zap.d.ts +6 -0
  262. package/dist/src/security/scanners/zap.d.ts.map +1 -0
  263. package/dist/src/security/scanners/zap.js +135 -0
  264. package/dist/src/security/types.d.ts +146 -0
  265. package/dist/src/security/types.d.ts.map +1 -0
  266. package/dist/src/security/types.js +6 -0
  267. package/dist/src/securityCoverage.d.ts +116 -0
  268. package/dist/src/securityCoverage.d.ts.map +1 -0
  269. package/dist/src/securityCoverage.js +725 -0
  270. package/dist/src/summary/buildSummary.d.ts +28 -0
  271. package/dist/src/summary/buildSummary.d.ts.map +1 -0
  272. package/dist/src/summary/buildSummary.js +257 -0
  273. package/dist/src/summary/evaluateMetrics.d.ts +31 -0
  274. package/dist/src/summary/evaluateMetrics.d.ts.map +1 -0
  275. package/dist/src/summary/evaluateMetrics.js +118 -0
  276. package/dist/src/summary/index.d.ts +10 -0
  277. package/dist/src/summary/index.d.ts.map +1 -0
  278. package/dist/src/summary/index.js +22 -0
  279. package/dist/src/summary/markdownRenderer.d.ts +139 -0
  280. package/dist/src/summary/markdownRenderer.d.ts.map +1 -0
  281. package/dist/src/summary/markdownRenderer.js +459 -0
  282. package/dist/src/summary/prSummary.d.ts +24 -0
  283. package/dist/src/summary/prSummary.d.ts.map +1 -0
  284. package/dist/src/summary/prSummary.js +233 -0
  285. package/dist/src/summary/summaryTypes.d.ts +35 -0
  286. package/dist/src/summary/summaryTypes.d.ts.map +1 -0
  287. package/dist/src/summary/summaryTypes.js +27 -0
  288. package/package.json +84 -0
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Business Rule Inference Engine
3
+ *
4
+ * When no `business-rules.yaml` file is present, this engine analyzes service
5
+ * source code to infer business constraints automatically.
6
+ *
7
+ * Inference sources:
8
+ * - Throw / raise statements gated on a condition (e.g. throw InsufficientFundsException)
9
+ * - Validation checks that return 4xx HTTP responses
10
+ * - Authorization logic (isAdmin, hasRole, etc.)
11
+ * - Null / empty guards
12
+ *
13
+ * Inferred rules are labeled `rule_source: "inferred"` and include a
14
+ * `source_location` field pointing back to the originating code.
15
+ *
16
+ * Generated artifact: reports/inferred-business-rules.json
17
+ */
18
+ export type RuleSource = 'inferred' | 'explicit';
19
+ export type RuleType = 'validation' | 'authorization' | 'business_logic' | 'error_flow';
20
+ export interface InferredBusinessRule {
21
+ /** Stable identifier derived from location + condition */
22
+ id: string;
23
+ /** Short human-readable name */
24
+ name: string;
25
+ rule_source: RuleSource;
26
+ type: RuleType;
27
+ /** Best-guess endpoint this rule applies to */
28
+ endpoint?: string;
29
+ /** The condition expression as a string */
30
+ condition: string;
31
+ /** Human-readable description of expected behavior */
32
+ expected_behavior: string;
33
+ /** File path + line number */
34
+ source_location: string;
35
+ /** Raw matched code snippet */
36
+ code_snippet: string;
37
+ }
38
+ export interface BusinessRuleInferenceResult {
39
+ rules: InferredBusinessRule[];
40
+ /** Number of service files analyzed */
41
+ filesAnalyzed: number;
42
+ /** Whether inference was used (true) or explicit files found (false) */
43
+ inferred: boolean;
44
+ /** Warnings emitted during analysis */
45
+ warnings: string[];
46
+ }
47
+ /**
48
+ * Infer business rules from a single service source file.
49
+ */
50
+ export declare function inferRulesFromFile(filePath: string): InferredBusinessRule[];
51
+ /**
52
+ * Run inference across all provided service source files.
53
+ *
54
+ * @param serviceFiles - Paths to service/application source files
55
+ * @param warnings - Mutable array; warnings are pushed here
56
+ */
57
+ export declare function inferBusinessRules(serviceFiles: string[], warnings?: string[]): BusinessRuleInferenceResult;
58
+ /**
59
+ * Write inferred business rules to the reports directory.
60
+ * Returns the path of the written file.
61
+ */
62
+ export declare function writeInferredBusinessRules(result: BusinessRuleInferenceResult, reportsDir: string): string;
63
+ //# sourceMappingURL=businessRuleInference.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"businessRuleInference.d.ts","sourceRoot":"","sources":["../../../src/inference/businessRuleInference.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAOH,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,UAAU,CAAC;AACjD,MAAM,MAAM,QAAQ,GAChB,YAAY,GACZ,eAAe,GACf,gBAAgB,GAChB,YAAY,CAAC;AAEjB,MAAM,WAAW,oBAAoB;IACnC,0DAA0D;IAC1D,EAAE,EAAE,MAAM,CAAC;IACX,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,UAAU,CAAC;IACxB,IAAI,EAAE,QAAQ,CAAC;IACf,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2CAA2C;IAC3C,SAAS,EAAE,MAAM,CAAC;IAClB,sDAAsD;IACtD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,8BAA8B;IAC9B,eAAe,EAAE,MAAM,CAAC;IACxB,+BAA+B;IAC/B,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,2BAA2B;IAC1C,KAAK,EAAE,oBAAoB,EAAE,CAAC;IAC9B,uCAAuC;IACvC,aAAa,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,QAAQ,EAAE,OAAO,CAAC;IAClB,uCAAuC;IACvC,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AA2ID;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,oBAAoB,EAAE,CA2C3E;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,MAAM,EAAE,EACtB,QAAQ,GAAE,MAAM,EAAO,GACtB,2BAA2B,CAuB7B;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,2BAA2B,EACnC,UAAU,EAAE,MAAM,GACjB,MAAM,CAYR"}
@@ -0,0 +1,268 @@
1
+ "use strict";
2
+ /**
3
+ * Business Rule Inference Engine
4
+ *
5
+ * When no `business-rules.yaml` file is present, this engine analyzes service
6
+ * source code to infer business constraints automatically.
7
+ *
8
+ * Inference sources:
9
+ * - Throw / raise statements gated on a condition (e.g. throw InsufficientFundsException)
10
+ * - Validation checks that return 4xx HTTP responses
11
+ * - Authorization logic (isAdmin, hasRole, etc.)
12
+ * - Null / empty guards
13
+ *
14
+ * Inferred rules are labeled `rule_source: "inferred"` and include a
15
+ * `source_location` field pointing back to the originating code.
16
+ *
17
+ * Generated artifact: reports/inferred-business-rules.json
18
+ */
19
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ var desc = Object.getOwnPropertyDescriptor(m, k);
22
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
23
+ desc = { enumerable: true, get: function() { return m[k]; } };
24
+ }
25
+ Object.defineProperty(o, k2, desc);
26
+ }) : (function(o, m, k, k2) {
27
+ if (k2 === undefined) k2 = k;
28
+ o[k2] = m[k];
29
+ }));
30
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
31
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
32
+ }) : function(o, v) {
33
+ o["default"] = v;
34
+ });
35
+ var __importStar = (this && this.__importStar) || (function () {
36
+ var ownKeys = function(o) {
37
+ ownKeys = Object.getOwnPropertyNames || function (o) {
38
+ var ar = [];
39
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
40
+ return ar;
41
+ };
42
+ return ownKeys(o);
43
+ };
44
+ return function (mod) {
45
+ if (mod && mod.__esModule) return mod;
46
+ var result = {};
47
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
48
+ __setModuleDefault(result, mod);
49
+ return result;
50
+ };
51
+ })();
52
+ Object.defineProperty(exports, "__esModule", { value: true });
53
+ exports.inferRulesFromFile = inferRulesFromFile;
54
+ exports.inferBusinessRules = inferBusinessRules;
55
+ exports.writeInferredBusinessRules = writeInferredBusinessRules;
56
+ const fs = __importStar(require("fs"));
57
+ const path = __importStar(require("path"));
58
+ const INFERENCE_PATTERNS = [
59
+ // ── throw / raise with an exception ──────────────────────────────────────
60
+ {
61
+ type: 'business_logic',
62
+ pattern: /throw\s+new\s+(\w+Exception|\w+Error)\s*\(([^)]*)\)/i,
63
+ nameTemplate: (m) => toSnakeCase(m[1]),
64
+ behaviorTemplate: (m) => `Operation rejected: ${m[1]}`,
65
+ conditionTemplate: (m) => `throws ${m[1]}(${m[2].trim()})`,
66
+ },
67
+ // Python raise
68
+ {
69
+ type: 'business_logic',
70
+ pattern: /raise\s+(\w+(?:Exception|Error))\s*(?:\(([^)]*)\))?/,
71
+ nameTemplate: (m) => toSnakeCase(m[1]),
72
+ behaviorTemplate: (m) => `Operation rejected: ${m[1]}`,
73
+ conditionTemplate: (m) => { var _a; return `raise ${m[1]}(${((_a = m[2]) !== null && _a !== void 0 ? _a : '').trim()})`; },
74
+ },
75
+ // Ruby raise
76
+ {
77
+ type: 'business_logic',
78
+ pattern: /raise\s+(\w+(?:Exception|Error))\s*(?:,\s*['"]([^'"]*)['"]\s*)?/,
79
+ nameTemplate: (m) => toSnakeCase(m[1]),
80
+ behaviorTemplate: (m) => `Operation rejected: ${m[1]}${m[2] ? ' — ' + m[2] : ''}`,
81
+ conditionTemplate: (m) => `raise ${m[1]}`,
82
+ },
83
+ // ── HTTP 4xx response returns ──────────────────────────────────────────────
84
+ {
85
+ type: 'validation',
86
+ pattern: /return\s+(?:ResponseEntity\.)?(?:status\s*\()?(4\d\d)(?:\))?/,
87
+ nameTemplate: (m) => `http_${m[1]}_response`,
88
+ behaviorTemplate: (m) => `Request rejected with HTTP ${m[1]}`,
89
+ conditionTemplate: (m) => `returns HTTP ${m[1]}`,
90
+ },
91
+ // Chained .status(4xx) calls: res.status(400).json(...)
92
+ {
93
+ type: 'validation',
94
+ pattern: /\.status\s*\(\s*(4\d\d)\s*\)/,
95
+ nameTemplate: (m) => `http_${m[1]}_response`,
96
+ behaviorTemplate: (m) => `Request rejected with HTTP ${m[1]}`,
97
+ conditionTemplate: (m) => `returns HTTP ${m[1]}`,
98
+ },
99
+ // Flask / FastAPI abort(4xx) or return 4xx
100
+ {
101
+ type: 'validation',
102
+ pattern: /abort\s*\(\s*(4\d\d)\s*\)/,
103
+ nameTemplate: (m) => `http_${m[1]}_abort`,
104
+ behaviorTemplate: (m) => `Request aborted with HTTP ${m[1]}`,
105
+ conditionTemplate: (m) => `abort(${m[1]})`,
106
+ },
107
+ // Rails render json: ..., status: :unprocessable_entity / :forbidden / :not_found
108
+ {
109
+ type: 'validation',
110
+ pattern: /render\s+json:.*,\s*status:\s*:(\w+)/,
111
+ nameTemplate: (m) => `rails_status_${m[1]}`,
112
+ behaviorTemplate: (m) => `Request rejected with status :${m[1]}`,
113
+ conditionTemplate: (m) => `render json with status :${m[1]}`,
114
+ },
115
+ // ── Null / blank guards ────────────────────────────────────────────────────
116
+ {
117
+ type: 'validation',
118
+ pattern: /if\s*\(?\s*([\w.]+)\s*(?:==\s*null|===\s*null|===\s*undefined|is\s+None|\.nil\?|\.blank\?|\.empty\?)\s*\)?/,
119
+ nameTemplate: (m) => `null_check_${toSnakeCase(m[1].replace(/\./g, '_'))}`,
120
+ behaviorTemplate: (m) => `${m[1]} must not be null/empty`,
121
+ conditionTemplate: (m) => `${m[1]} == null`,
122
+ },
123
+ // ── Authorization / permission guards ─────────────────────────────────────
124
+ {
125
+ type: 'authorization',
126
+ pattern: /if\s*\(?\s*!?\s*(?:user\.)?(?:isAdmin|hasRole|hasPermission|can|authorize|authenticated)\s*\(?([^)]*)\)?/i,
127
+ nameTemplate: (m) => `authorization_${toSnakeCase(m[1] || 'check')}`,
128
+ behaviorTemplate: (m) => `Access denied — insufficient permissions`,
129
+ conditionTemplate: (m) => { var _a; return `!isAuthorized(${((_a = m[1]) !== null && _a !== void 0 ? _a : '').trim()})`; },
130
+ },
131
+ // ── Balance / capacity / limit checks ─────────────────────────────────────
132
+ {
133
+ type: 'business_logic',
134
+ pattern: /if\s*\(?\s*(\w+)\s*>\s*(\w+)\s*\)?.*(?:throw|raise|return\s+4)/i,
135
+ nameTemplate: (m) => `limit_exceeded_${toSnakeCase(m[1])}`,
136
+ behaviorTemplate: (m) => `${m[1]} must not exceed ${m[2]}`,
137
+ conditionTemplate: (m) => `${m[1]} > ${m[2]}`,
138
+ },
139
+ ];
140
+ // ─── Endpoint heuristics ──────────────────────────────────────────────────────
141
+ /** Attempt to associate a rule with the nearest HTTP route/endpoint annotation. */
142
+ function guessEndpoint(lines, ruleLineIdx) {
143
+ // Search up to 40 lines above for common routing patterns
144
+ const lookupLines = lines.slice(Math.max(0, ruleLineIdx - 40), ruleLineIdx);
145
+ // Spring: @GetMapping("/path") / @PostMapping / @RequestMapping
146
+ for (let i = lookupLines.length - 1; i >= 0; i--) {
147
+ const m = lookupLines[i].match(/@(Get|Post|Put|Patch|Delete|Request)Mapping\s*\(\s*["']([^"']+)["']/i);
148
+ if (m)
149
+ return `${m[1].toUpperCase()} ${m[2]}`;
150
+ }
151
+ // FastAPI: @router.get("/path") / @app.post
152
+ for (let i = lookupLines.length - 1; i >= 0; i--) {
153
+ const m = lookupLines[i].match(/@(?:router|app)\.(get|post|put|patch|delete)\s*\(\s*["']([^"']+)["']/i);
154
+ if (m)
155
+ return `${m[1].toUpperCase()} ${m[2]}`;
156
+ }
157
+ // Express: router.get('/path') / app.post('/path')
158
+ for (let i = lookupLines.length - 1; i >= 0; i--) {
159
+ const m = lookupLines[i].match(/(?:router|app)\.(get|post|put|patch|delete)\s*\(\s*["']([^"']+)["']/i);
160
+ if (m)
161
+ return `${m[1].toUpperCase()} ${m[2]}`;
162
+ }
163
+ // Rails: resources :name / get '/path'
164
+ for (let i = lookupLines.length - 1; i >= 0; i--) {
165
+ const m = lookupLines[i].match(/(?:get|post|put|patch|delete)\s*["']([^"']+)["']/i);
166
+ if (m)
167
+ return `${lookupLines[i].trim().split(/\s/)[0].toUpperCase()} ${m[1]}`;
168
+ }
169
+ return undefined;
170
+ }
171
+ // ─── Core inference function ─────────────────────────────────────────────────
172
+ /**
173
+ * Infer business rules from a single service source file.
174
+ */
175
+ function inferRulesFromFile(filePath) {
176
+ let content;
177
+ try {
178
+ content = fs.readFileSync(filePath, 'utf-8');
179
+ }
180
+ catch {
181
+ return [];
182
+ }
183
+ const lines = content.split('\n');
184
+ const rules = [];
185
+ const seen = new Set();
186
+ for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
187
+ const line = lines[lineIdx];
188
+ for (const ip of INFERENCE_PATTERNS) {
189
+ const match = line.match(ip.pattern);
190
+ if (!match)
191
+ continue;
192
+ const condition = ip.conditionTemplate(match);
193
+ const sourceLocation = `${filePath}:${lineIdx + 1}`;
194
+ const dedupKey = `${filePath}:${lineIdx}:${condition}`;
195
+ if (seen.has(dedupKey))
196
+ continue;
197
+ seen.add(dedupKey);
198
+ const name = ip.nameTemplate(match);
199
+ const id = `${toSnakeCase(path.basename(filePath, path.extname(filePath)))}_${name}_${lineIdx + 1}`;
200
+ const endpoint = guessEndpoint(lines, lineIdx);
201
+ rules.push({
202
+ id,
203
+ name,
204
+ rule_source: 'inferred',
205
+ type: ip.type,
206
+ endpoint,
207
+ condition,
208
+ expected_behavior: ip.behaviorTemplate(match),
209
+ source_location: sourceLocation,
210
+ code_snippet: line.trim().slice(0, 200),
211
+ });
212
+ }
213
+ }
214
+ return rules;
215
+ }
216
+ /**
217
+ * Run inference across all provided service source files.
218
+ *
219
+ * @param serviceFiles - Paths to service/application source files
220
+ * @param warnings - Mutable array; warnings are pushed here
221
+ */
222
+ function inferBusinessRules(serviceFiles, warnings = []) {
223
+ if (serviceFiles.length === 0) {
224
+ warnings.push('No service source files provided; business rule inference skipped.');
225
+ return { rules: [], filesAnalyzed: 0, inferred: true, warnings };
226
+ }
227
+ const allRules = [];
228
+ let filesAnalyzed = 0;
229
+ for (const fp of serviceFiles) {
230
+ const fileRules = inferRulesFromFile(fp);
231
+ if (fileRules.length > 0) {
232
+ allRules.push(...fileRules);
233
+ }
234
+ filesAnalyzed++;
235
+ }
236
+ return {
237
+ rules: allRules,
238
+ filesAnalyzed,
239
+ inferred: true,
240
+ warnings,
241
+ };
242
+ }
243
+ /**
244
+ * Write inferred business rules to the reports directory.
245
+ * Returns the path of the written file.
246
+ */
247
+ function writeInferredBusinessRules(result, reportsDir) {
248
+ fs.mkdirSync(reportsDir, { recursive: true });
249
+ const outputPath = path.join(reportsDir, 'inferred-business-rules.json');
250
+ const output = {
251
+ generated_at: new Date().toISOString(),
252
+ rule_source: 'inferred',
253
+ files_analyzed: result.filesAnalyzed,
254
+ rule_count: result.rules.length,
255
+ rules: result.rules,
256
+ };
257
+ fs.writeFileSync(outputPath, JSON.stringify(output, null, 2), 'utf-8');
258
+ return outputPath;
259
+ }
260
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
261
+ function toSnakeCase(str) {
262
+ return str
263
+ .replace(/([A-Z])/g, '_$1')
264
+ .toLowerCase()
265
+ .replace(/^_/, '')
266
+ .replace(/[^a-z0-9_]/g, '_')
267
+ .replace(/__+/g, '_');
268
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Integration Flow Inference Engine
3
+ *
4
+ * When no `integration-flows.yaml` file is present, this engine constructs
5
+ * integration flows automatically by analyzing:
6
+ *
7
+ * - Sequences of HTTP calls within a single test function / scenario
8
+ * - Cucumber scenario steps (Given / When / Then)
9
+ * - Chained request helper calls (createUser → login → createPayment)
10
+ *
11
+ * Inferred flows are labeled and include source traceability.
12
+ *
13
+ * Generated artifact: reports/inferred-integration-flows.json
14
+ */
15
+ export type FlowSource = 'inferred' | 'explicit';
16
+ export interface FlowStep {
17
+ /** HTTP method */
18
+ method: string;
19
+ /** URL path (may contain path params) */
20
+ path: string;
21
+ /** Step description */
22
+ description?: string;
23
+ }
24
+ export interface InferredIntegrationFlow {
25
+ /** Stable identifier */
26
+ id: string;
27
+ /** Human-readable flow name */
28
+ name: string;
29
+ flow_source: FlowSource;
30
+ /** Steps in execution order */
31
+ steps: FlowStep[];
32
+ /** File and line where the sequence was found */
33
+ source_location: string;
34
+ /** Test function / scenario name (if detectable) */
35
+ test_name?: string;
36
+ }
37
+ export interface IntegrationFlowInferenceResult {
38
+ flows: InferredIntegrationFlow[];
39
+ filesAnalyzed: number;
40
+ inferred: boolean;
41
+ warnings: string[];
42
+ }
43
+ /**
44
+ * Infer integration flows from a single test file.
45
+ */
46
+ export declare function inferFlowsFromFile(filePath: string): InferredIntegrationFlow[];
47
+ /**
48
+ * Run flow inference across all provided test files.
49
+ */
50
+ export declare function inferIntegrationFlows(testFiles: string[], warnings?: string[]): IntegrationFlowInferenceResult;
51
+ /**
52
+ * Write inferred integration flows to the reports directory.
53
+ * Returns the path of the written file.
54
+ */
55
+ export declare function writeInferredIntegrationFlows(result: IntegrationFlowInferenceResult, reportsDir: string): string;
56
+ //# sourceMappingURL=integrationFlowInference.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integrationFlowInference.d.ts","sourceRoot":"","sources":["../../../src/inference/integrationFlowInference.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAOH,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,UAAU,CAAC;AAEjD,MAAM,WAAW,QAAQ;IACvB,kBAAkB;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,uBAAuB;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,uBAAuB;IACtC,wBAAwB;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,UAAU,CAAC;IACxB,+BAA+B;IAC/B,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,iDAAiD;IACjD,eAAe,EAAE,MAAM,CAAC;IACxB,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,8BAA8B;IAC7C,KAAK,EAAE,uBAAuB,EAAE,CAAC;IACjC,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AA4KD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,uBAAuB,EAAE,CAW9E;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,MAAM,EAAE,EACnB,QAAQ,GAAE,MAAM,EAAO,GACtB,8BAA8B,CA2BhC;AAED;;;GAGG;AACH,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,8BAA8B,EACtC,UAAU,EAAE,MAAM,GACjB,MAAM,CAYR"}
@@ -0,0 +1,266 @@
1
+ "use strict";
2
+ /**
3
+ * Integration Flow Inference Engine
4
+ *
5
+ * When no `integration-flows.yaml` file is present, this engine constructs
6
+ * integration flows automatically by analyzing:
7
+ *
8
+ * - Sequences of HTTP calls within a single test function / scenario
9
+ * - Cucumber scenario steps (Given / When / Then)
10
+ * - Chained request helper calls (createUser → login → createPayment)
11
+ *
12
+ * Inferred flows are labeled and include source traceability.
13
+ *
14
+ * Generated artifact: reports/inferred-integration-flows.json
15
+ */
16
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ var desc = Object.getOwnPropertyDescriptor(m, k);
19
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
20
+ desc = { enumerable: true, get: function() { return m[k]; } };
21
+ }
22
+ Object.defineProperty(o, k2, desc);
23
+ }) : (function(o, m, k, k2) {
24
+ if (k2 === undefined) k2 = k;
25
+ o[k2] = m[k];
26
+ }));
27
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
28
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
29
+ }) : function(o, v) {
30
+ o["default"] = v;
31
+ });
32
+ var __importStar = (this && this.__importStar) || (function () {
33
+ var ownKeys = function(o) {
34
+ ownKeys = Object.getOwnPropertyNames || function (o) {
35
+ var ar = [];
36
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
37
+ return ar;
38
+ };
39
+ return ownKeys(o);
40
+ };
41
+ return function (mod) {
42
+ if (mod && mod.__esModule) return mod;
43
+ var result = {};
44
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
45
+ __setModuleDefault(result, mod);
46
+ return result;
47
+ };
48
+ })();
49
+ Object.defineProperty(exports, "__esModule", { value: true });
50
+ exports.inferFlowsFromFile = inferFlowsFromFile;
51
+ exports.inferIntegrationFlows = inferIntegrationFlows;
52
+ exports.writeInferredIntegrationFlows = writeInferredIntegrationFlows;
53
+ const fs = __importStar(require("fs"));
54
+ const path = __importStar(require("path"));
55
+ const HTTP_CALL_PATTERNS = [
56
+ // Axios / fetch / supertest: axios.get('/path'), .post('/path'), request.get('/path')
57
+ { pattern: /(?:axios|request|client|api|http|supertest)\.(get|post|put|patch|delete|head)\s*\(\s*['"`]([^'"`]+)['"`]/i, methodGroup: 1, pathGroup: 2 },
58
+ // RestAssured / Spring MockMvc: given().when().get("/path")
59
+ { pattern: /\.(?:when\(\)\.)?(get|post|put|patch|delete)\s*\(\s*['"`]([^'"`]+)['"`]/i, methodGroup: 1, pathGroup: 2 },
60
+ // Python requests: requests.get('http://...', ...) / client.post('/path')
61
+ { pattern: /(?:requests|client|self\.client|self\.app\.test_client\(\))\.(get|post|put|patch|delete)\s*\(\s*['"`]([^'"`\s]+)['"`]/i, methodGroup: 1, pathGroup: 2 },
62
+ // Ruby HTTParty / Rails: get '/path', post '/path'
63
+ { pattern: /\b(get|post|put|patch|delete)\s+['"`]([^'"`\s]+)['"`]/i, methodGroup: 1, pathGroup: 2 },
64
+ // fetch('/path', { method: 'POST' })
65
+ { pattern: /fetch\s*\(\s*['"`]([^'"`]+)['"`]\s*,\s*\{[^}]*method\s*:\s*['"`](GET|POST|PUT|PATCH|DELETE|HEAD)['"`]/i, methodGroup: 2, pathGroup: 1 },
66
+ ];
67
+ /** Patterns for test function / scenario boundaries */
68
+ const TEST_FUNCTION_PATTERNS = [
69
+ // Jest / Mocha / RSpec: it('name', ...) / test('name', ...)
70
+ /(?:^|\s)(?:it|test|describe)\s*\(\s*['"`]([^'"`]+)['"`]/,
71
+ // JUnit: @Test ... void testSomething()
72
+ /@Test[\s\S]{0,200}void\s+(\w+)/,
73
+ // Python pytest: def test_something():
74
+ /def\s+(test_\w+)\s*\(/,
75
+ // Ruby RSpec: it "name do"
76
+ /it\s+['"]([^'"]+)['"]\s+do/,
77
+ // Cucumber scenario
78
+ /Scenario(?:\s+Outline)?:\s*(.+)/,
79
+ ];
80
+ function extractHttpCallsFromLines(lines) {
81
+ const calls = [];
82
+ for (let i = 0; i < lines.length; i++) {
83
+ const line = lines[i];
84
+ for (const p of HTTP_CALL_PATTERNS) {
85
+ const m = line.match(p.pattern);
86
+ if (m) {
87
+ const method = m[p.methodGroup].toUpperCase();
88
+ const rawPath = m[p.pathGroup];
89
+ // Skip unlikely paths (full URLs with domain, non-path values)
90
+ if (rawPath.startsWith('http') && !rawPath.includes('/api'))
91
+ continue;
92
+ const cleanPath = extractPathFromUrl(rawPath);
93
+ if (!cleanPath)
94
+ continue;
95
+ calls.push({ method, path: cleanPath, lineIdx: i });
96
+ break; // only first pattern match per line
97
+ }
98
+ }
99
+ }
100
+ return calls;
101
+ }
102
+ function extractPathFromUrl(raw) {
103
+ if (raw.startsWith('/'))
104
+ return raw;
105
+ try {
106
+ const u = new URL(raw);
107
+ return u.pathname || undefined;
108
+ }
109
+ catch {
110
+ // not a full URL — check plain path-like
111
+ if (/^[\w/{}._-]+$/.test(raw) && raw.includes('/'))
112
+ return raw;
113
+ return undefined;
114
+ }
115
+ }
116
+ // ─── Test boundary detection ──────────────────────────────────────────────────
117
+ function detectTestName(lines, lineIdx) {
118
+ // Look up to 20 lines above for a test function boundary
119
+ const window = lines.slice(Math.max(0, lineIdx - 20), lineIdx + 1);
120
+ for (let i = window.length - 1; i >= 0; i--) {
121
+ for (const p of TEST_FUNCTION_PATTERNS) {
122
+ const m = window[i].match(p);
123
+ if (m)
124
+ return m[1].trim();
125
+ }
126
+ }
127
+ return undefined;
128
+ }
129
+ // ─── Flow grouping ────────────────────────────────────────────────────────────
130
+ /**
131
+ * Group extracted HTTP calls into flows.
132
+ *
133
+ * Strategy: calls within the same test function (± 50 lines) form a flow.
134
+ * Calls with no shared context become single-step "isolated" flows.
135
+ */
136
+ function groupCallsIntoFlows(calls, filePath, lines) {
137
+ if (calls.length === 0)
138
+ return [];
139
+ const flows = [];
140
+ let currentGroup = [];
141
+ let currentTestName;
142
+ for (let i = 0; i < calls.length; i++) {
143
+ const call = calls[i];
144
+ const tName = detectTestName(lines, call.lineIdx);
145
+ if (i === 0) {
146
+ currentTestName = tName;
147
+ currentGroup = [call];
148
+ }
149
+ else {
150
+ const prev = calls[i - 1];
151
+ // Same test or within 50 lines → same flow
152
+ if ((tName && tName === currentTestName) ||
153
+ (!tName && call.lineIdx - prev.lineIdx <= 50)) {
154
+ currentGroup.push(call);
155
+ }
156
+ else {
157
+ // Flush current group as a flow
158
+ if (currentGroup.length >= 2) {
159
+ flows.push(buildFlow(currentGroup, currentTestName, filePath));
160
+ }
161
+ currentTestName = tName;
162
+ currentGroup = [call];
163
+ }
164
+ }
165
+ }
166
+ // Flush last group
167
+ if (currentGroup.length >= 2) {
168
+ flows.push(buildFlow(currentGroup, currentTestName, filePath));
169
+ }
170
+ return flows;
171
+ }
172
+ function buildFlow(calls, testName, filePath) {
173
+ const steps = calls.map((c) => ({
174
+ method: c.method,
175
+ path: c.path,
176
+ }));
177
+ const firstLine = calls[0].lineIdx + 1;
178
+ const baseName = path.basename(filePath, path.extname(filePath));
179
+ const flowLabel = testName !== null && testName !== void 0 ? testName : `flow_at_line_${firstLine}`;
180
+ const id = `${toSnakeCase(baseName)}_${toSnakeCase(flowLabel)}`;
181
+ return {
182
+ id,
183
+ name: testName ? humanize(testName) : `Flow in ${path.basename(filePath)}:${firstLine}`,
184
+ flow_source: 'inferred',
185
+ steps,
186
+ source_location: `${filePath}:${firstLine}`,
187
+ test_name: testName,
188
+ };
189
+ }
190
+ // ─── Core inference function ──────────────────────────────────────────────────
191
+ /**
192
+ * Infer integration flows from a single test file.
193
+ */
194
+ function inferFlowsFromFile(filePath) {
195
+ let content;
196
+ try {
197
+ content = fs.readFileSync(filePath, 'utf-8');
198
+ }
199
+ catch {
200
+ return [];
201
+ }
202
+ const lines = content.split('\n');
203
+ const calls = extractHttpCallsFromLines(lines);
204
+ return groupCallsIntoFlows(calls, filePath, lines);
205
+ }
206
+ /**
207
+ * Run flow inference across all provided test files.
208
+ */
209
+ function inferIntegrationFlows(testFiles, warnings = []) {
210
+ if (testFiles.length === 0) {
211
+ warnings.push('No test files provided; integration flow inference skipped.');
212
+ return { flows: [], filesAnalyzed: 0, inferred: true, warnings };
213
+ }
214
+ const allFlows = [];
215
+ let filesAnalyzed = 0;
216
+ for (const fp of testFiles) {
217
+ const fileFlows = inferFlowsFromFile(fp);
218
+ if (fileFlows.length > 0) {
219
+ allFlows.push(...fileFlows);
220
+ }
221
+ filesAnalyzed++;
222
+ }
223
+ if (allFlows.length === 0) {
224
+ warnings.push('No multi-step HTTP call sequences detected in test files; integration flow inference produced no results.');
225
+ }
226
+ return {
227
+ flows: allFlows,
228
+ filesAnalyzed,
229
+ inferred: true,
230
+ warnings,
231
+ };
232
+ }
233
+ /**
234
+ * Write inferred integration flows to the reports directory.
235
+ * Returns the path of the written file.
236
+ */
237
+ function writeInferredIntegrationFlows(result, reportsDir) {
238
+ fs.mkdirSync(reportsDir, { recursive: true });
239
+ const outputPath = path.join(reportsDir, 'inferred-integration-flows.json');
240
+ const output = {
241
+ generated_at: new Date().toISOString(),
242
+ flow_source: 'inferred',
243
+ files_analyzed: result.filesAnalyzed,
244
+ flow_count: result.flows.length,
245
+ flows: result.flows,
246
+ };
247
+ fs.writeFileSync(outputPath, JSON.stringify(output, null, 2), 'utf-8');
248
+ return outputPath;
249
+ }
250
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
251
+ function toSnakeCase(str) {
252
+ return str
253
+ .replace(/([A-Z])/g, '_$1')
254
+ .toLowerCase()
255
+ .replace(/^_/, '')
256
+ .replace(/[^a-z0-9_]/g, '_')
257
+ .replace(/__+/g, '_');
258
+ }
259
+ function humanize(str) {
260
+ return str
261
+ .replace(/_/g, ' ')
262
+ .replace(/([A-Z])/g, ' $1')
263
+ .replace(/\s+/g, ' ')
264
+ .trim()
265
+ .replace(/^./, (c) => c.toUpperCase());
266
+ }