@vk.amogh/trace 2.1.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 (249) hide show
  1. package/README.md +208 -0
  2. package/bin/trace.js +112 -0
  3. package/package.json +46 -0
  4. package/pyproject.toml +39 -0
  5. package/src/trace_engine/__init__.py +8 -0
  6. package/src/trace_engine/__pycache__/__init__.cpython-311.pyc +0 -0
  7. package/src/trace_engine/__pycache__/cli.cpython-311.pyc +0 -0
  8. package/src/trace_engine/__pycache__/doctor.cpython-311.pyc +0 -0
  9. package/src/trace_engine/__pycache__/interactive.cpython-311.pyc +0 -0
  10. package/src/trace_engine/__pycache__/verify.cpython-311.pyc +0 -0
  11. package/src/trace_engine/ai/__init__.py +7 -0
  12. package/src/trace_engine/ai/__pycache__/__init__.cpython-311.pyc +0 -0
  13. package/src/trace_engine/ai/__pycache__/base.cpython-311.pyc +0 -0
  14. package/src/trace_engine/ai/__pycache__/ollama.cpython-311.pyc +0 -0
  15. package/src/trace_engine/ai/__pycache__/planner.cpython-311.pyc +0 -0
  16. package/src/trace_engine/ai/base.py +23 -0
  17. package/src/trace_engine/ai/ollama.py +50 -0
  18. package/src/trace_engine/ai/planner.py +40 -0
  19. package/src/trace_engine/apm/__init__.py +18 -0
  20. package/src/trace_engine/apm/__pycache__/__init__.cpython-311.pyc +0 -0
  21. package/src/trace_engine/apm/__pycache__/builder.cpython-311.pyc +0 -0
  22. package/src/trace_engine/apm/__pycache__/edges.cpython-311.pyc +0 -0
  23. package/src/trace_engine/apm/__pycache__/model.cpython-311.pyc +0 -0
  24. package/src/trace_engine/apm/__pycache__/nodes.cpython-311.pyc +0 -0
  25. package/src/trace_engine/apm/__pycache__/serialization.cpython-311.pyc +0 -0
  26. package/src/trace_engine/apm/builder.py +208 -0
  27. package/src/trace_engine/apm/edges.py +25 -0
  28. package/src/trace_engine/apm/model.py +107 -0
  29. package/src/trace_engine/apm/nodes.py +27 -0
  30. package/src/trace_engine/apm/serialization.py +105 -0
  31. package/src/trace_engine/benchmark/__init__.py +5 -0
  32. package/src/trace_engine/benchmark/__pycache__/__init__.cpython-311.pyc +0 -0
  33. package/src/trace_engine/benchmark/__pycache__/owasp.cpython-311.pyc +0 -0
  34. package/src/trace_engine/benchmark/owasp.py +183 -0
  35. package/src/trace_engine/cli.py +1184 -0
  36. package/src/trace_engine/config/__init__.py +35 -0
  37. package/src/trace_engine/config/__pycache__/__init__.cpython-311.pyc +0 -0
  38. package/src/trace_engine/config/__pycache__/defaults.cpython-311.pyc +0 -0
  39. package/src/trace_engine/config/__pycache__/loader.cpython-311.pyc +0 -0
  40. package/src/trace_engine/config/__pycache__/settings.cpython-311.pyc +0 -0
  41. package/src/trace_engine/config/defaults.py +48 -0
  42. package/src/trace_engine/config/loader.py +64 -0
  43. package/src/trace_engine/config/settings.py +72 -0
  44. package/src/trace_engine/doctor.py +250 -0
  45. package/src/trace_engine/findings/__init__.py +15 -0
  46. package/src/trace_engine/findings/__pycache__/__init__.cpython-311.pyc +0 -0
  47. package/src/trace_engine/findings/__pycache__/correlate.cpython-311.pyc +0 -0
  48. package/src/trace_engine/findings/__pycache__/model.cpython-311.pyc +0 -0
  49. package/src/trace_engine/findings/__pycache__/recommendations.cpython-311.pyc +0 -0
  50. package/src/trace_engine/findings/__pycache__/store.cpython-311.pyc +0 -0
  51. package/src/trace_engine/findings/correlate.py +103 -0
  52. package/src/trace_engine/findings/model.py +40 -0
  53. package/src/trace_engine/findings/recommendations.py +35 -0
  54. package/src/trace_engine/findings/store.py +39 -0
  55. package/src/trace_engine/framework/__init__.py +58 -0
  56. package/src/trace_engine/framework/__pycache__/__init__.cpython-311.pyc +0 -0
  57. package/src/trace_engine/framework/__pycache__/base.cpython-311.pyc +0 -0
  58. package/src/trace_engine/framework/__pycache__/csharp.cpython-311.pyc +0 -0
  59. package/src/trace_engine/framework/__pycache__/dart.cpython-311.pyc +0 -0
  60. package/src/trace_engine/framework/__pycache__/django.cpython-311.pyc +0 -0
  61. package/src/trace_engine/framework/__pycache__/express.cpython-311.pyc +0 -0
  62. package/src/trace_engine/framework/__pycache__/fastapi.cpython-311.pyc +0 -0
  63. package/src/trace_engine/framework/__pycache__/flask.cpython-311.pyc +0 -0
  64. package/src/trace_engine/framework/__pycache__/go.cpython-311.pyc +0 -0
  65. package/src/trace_engine/framework/__pycache__/nextjs.cpython-311.pyc +0 -0
  66. package/src/trace_engine/framework/__pycache__/php.cpython-311.pyc +0 -0
  67. package/src/trace_engine/framework/__pycache__/react_router.cpython-311.pyc +0 -0
  68. package/src/trace_engine/framework/__pycache__/ruby.cpython-311.pyc +0 -0
  69. package/src/trace_engine/framework/__pycache__/rust.cpython-311.pyc +0 -0
  70. package/src/trace_engine/framework/__pycache__/springboot.cpython-311.pyc +0 -0
  71. package/src/trace_engine/framework/base.py +49 -0
  72. package/src/trace_engine/framework/csharp.py +111 -0
  73. package/src/trace_engine/framework/dart.py +152 -0
  74. package/src/trace_engine/framework/django.py +188 -0
  75. package/src/trace_engine/framework/express.py +82 -0
  76. package/src/trace_engine/framework/fastapi.py +135 -0
  77. package/src/trace_engine/framework/flask.py +108 -0
  78. package/src/trace_engine/framework/go.py +94 -0
  79. package/src/trace_engine/framework/nextjs.py +200 -0
  80. package/src/trace_engine/framework/php.py +98 -0
  81. package/src/trace_engine/framework/react_router.py +331 -0
  82. package/src/trace_engine/framework/ruby.py +69 -0
  83. package/src/trace_engine/framework/rust.py +101 -0
  84. package/src/trace_engine/framework/springboot.py +145 -0
  85. package/src/trace_engine/harness/__init__.py +19 -0
  86. package/src/trace_engine/harness/__pycache__/__init__.cpython-311.pyc +0 -0
  87. package/src/trace_engine/harness/__pycache__/benchmark.cpython-311.pyc +0 -0
  88. package/src/trace_engine/harness/__pycache__/context.cpython-311.pyc +0 -0
  89. package/src/trace_engine/harness/__pycache__/engine.cpython-311.pyc +0 -0
  90. package/src/trace_engine/harness/__pycache__/patcher.cpython-311.pyc +0 -0
  91. package/src/trace_engine/harness/__pycache__/remediators.cpython-311.pyc +0 -0
  92. package/src/trace_engine/harness/benchmark.py +55 -0
  93. package/src/trace_engine/harness/context.py +49 -0
  94. package/src/trace_engine/harness/engine.py +187 -0
  95. package/src/trace_engine/harness/patcher.py +143 -0
  96. package/src/trace_engine/harness/remediators.py +307 -0
  97. package/src/trace_engine/ingest/__init__.py +15 -0
  98. package/src/trace_engine/ingest/__pycache__/__init__.cpython-311.pyc +0 -0
  99. package/src/trace_engine/ingest/__pycache__/files.cpython-311.pyc +0 -0
  100. package/src/trace_engine/ingest/__pycache__/hashing.cpython-311.pyc +0 -0
  101. package/src/trace_engine/ingest/__pycache__/ignore.cpython-311.pyc +0 -0
  102. package/src/trace_engine/ingest/__pycache__/repository.cpython-311.pyc +0 -0
  103. package/src/trace_engine/ingest/files.py +60 -0
  104. package/src/trace_engine/ingest/hashing.py +21 -0
  105. package/src/trace_engine/ingest/ignore.py +108 -0
  106. package/src/trace_engine/ingest/repository.py +50 -0
  107. package/src/trace_engine/intelligence/__init__.py +15 -0
  108. package/src/trace_engine/intelligence/__pycache__/__init__.cpython-311.pyc +0 -0
  109. package/src/trace_engine/intelligence/__pycache__/evaluation.cpython-311.pyc +0 -0
  110. package/src/trace_engine/intelligence/__pycache__/orchestrator.cpython-311.pyc +0 -0
  111. package/src/trace_engine/intelligence/__pycache__/tracebench.cpython-311.pyc +0 -0
  112. package/src/trace_engine/intelligence/evaluation.py +828 -0
  113. package/src/trace_engine/intelligence/laya/__init__.py +19 -0
  114. package/src/trace_engine/intelligence/laya/__pycache__/__init__.cpython-311.pyc +0 -0
  115. package/src/trace_engine/intelligence/laya/__pycache__/prompts.cpython-311.pyc +0 -0
  116. package/src/trace_engine/intelligence/laya/__pycache__/router.cpython-311.pyc +0 -0
  117. package/src/trace_engine/intelligence/laya/__pycache__/schemas.cpython-311.pyc +0 -0
  118. package/src/trace_engine/intelligence/laya/__pycache__/telemetry.cpython-311.pyc +0 -0
  119. package/src/trace_engine/intelligence/laya/__pycache__/thresholds.cpython-311.pyc +0 -0
  120. package/src/trace_engine/intelligence/laya/prompts.py +67 -0
  121. package/src/trace_engine/intelligence/laya/router.py +352 -0
  122. package/src/trace_engine/intelligence/laya/schemas.py +56 -0
  123. package/src/trace_engine/intelligence/laya/telemetry.py +48 -0
  124. package/src/trace_engine/intelligence/laya/thresholds.py +12 -0
  125. package/src/trace_engine/intelligence/orchestrator.py +130 -0
  126. package/src/trace_engine/intelligence/securebert/__init__.py +6 -0
  127. package/src/trace_engine/intelligence/securebert/__pycache__/__init__.cpython-311.pyc +0 -0
  128. package/src/trace_engine/intelligence/securebert/__pycache__/cache.cpython-311.pyc +0 -0
  129. package/src/trace_engine/intelligence/securebert/__pycache__/classifier.cpython-311.pyc +0 -0
  130. package/src/trace_engine/intelligence/securebert/cache.py +37 -0
  131. package/src/trace_engine/intelligence/securebert/classifier.py +240 -0
  132. package/src/trace_engine/intelligence/tracebench.py +61 -0
  133. package/src/trace_engine/intelligence/training/__init__.py +21 -0
  134. package/src/trace_engine/intelligence/training/__pycache__/__init__.cpython-311.pyc +0 -0
  135. package/src/trace_engine/intelligence/training/__pycache__/dataset.cpython-311.pyc +0 -0
  136. package/src/trace_engine/intelligence/training/__pycache__/dataset_importers.cpython-311.pyc +0 -0
  137. package/src/trace_engine/intelligence/training/__pycache__/laya_trainer.cpython-311.pyc +0 -0
  138. package/src/trace_engine/intelligence/training/__pycache__/lora_system2.cpython-311.pyc +0 -0
  139. package/src/trace_engine/intelligence/training/__pycache__/morefixes_pipeline.cpython-311.pyc +0 -0
  140. package/src/trace_engine/intelligence/training/__pycache__/slicer.cpython-311.pyc +0 -0
  141. package/src/trace_engine/intelligence/training/__pycache__/train_all.cpython-311.pyc +0 -0
  142. package/src/trace_engine/intelligence/training/__pycache__/trainer.cpython-311.pyc +0 -0
  143. package/src/trace_engine/intelligence/training/dataset.py +320 -0
  144. package/src/trace_engine/intelligence/training/dataset_importers.py +349 -0
  145. package/src/trace_engine/intelligence/training/laya_trainer.py +678 -0
  146. package/src/trace_engine/intelligence/training/lora_system2.py +162 -0
  147. package/src/trace_engine/intelligence/training/morefixes_pipeline.py +463 -0
  148. package/src/trace_engine/intelligence/training/slicer.py +129 -0
  149. package/src/trace_engine/intelligence/training/train_all.py +1009 -0
  150. package/src/trace_engine/intelligence/training/trainer.py +321 -0
  151. package/src/trace_engine/interactive.py +623 -0
  152. package/src/trace_engine/mcp/__init__.py +6 -0
  153. package/src/trace_engine/mcp/__pycache__/__init__.cpython-311.pyc +0 -0
  154. package/src/trace_engine/mcp/__pycache__/config.cpython-311.pyc +0 -0
  155. package/src/trace_engine/mcp/__pycache__/server.cpython-311.pyc +0 -0
  156. package/src/trace_engine/mcp/config.py +42 -0
  157. package/src/trace_engine/mcp/server.py +398 -0
  158. package/src/trace_engine/output/__init__.py +28 -0
  159. package/src/trace_engine/output/__pycache__/__init__.cpython-311.pyc +0 -0
  160. package/src/trace_engine/output/__pycache__/html.cpython-311.pyc +0 -0
  161. package/src/trace_engine/output/__pycache__/markdown.cpython-311.pyc +0 -0
  162. package/src/trace_engine/output/__pycache__/sarif.cpython-311.pyc +0 -0
  163. package/src/trace_engine/output/__pycache__/terminal.cpython-311.pyc +0 -0
  164. package/src/trace_engine/output/html.py +142 -0
  165. package/src/trace_engine/output/markdown.py +65 -0
  166. package/src/trace_engine/output/sarif.py +180 -0
  167. package/src/trace_engine/output/terminal.py +537 -0
  168. package/src/trace_engine/parsing/__init__.py +26 -0
  169. package/src/trace_engine/parsing/__pycache__/__init__.cpython-311.pyc +0 -0
  170. package/src/trace_engine/parsing/__pycache__/calls.cpython-311.pyc +0 -0
  171. package/src/trace_engine/parsing/__pycache__/language.cpython-311.pyc +0 -0
  172. package/src/trace_engine/parsing/__pycache__/locations.cpython-311.pyc +0 -0
  173. package/src/trace_engine/parsing/__pycache__/parser.cpython-311.pyc +0 -0
  174. package/src/trace_engine/parsing/__pycache__/symbols.cpython-311.pyc +0 -0
  175. package/src/trace_engine/parsing/calls.py +14 -0
  176. package/src/trace_engine/parsing/language.py +24 -0
  177. package/src/trace_engine/parsing/locations.py +17 -0
  178. package/src/trace_engine/parsing/parser.py +465 -0
  179. package/src/trace_engine/parsing/symbols.py +45 -0
  180. package/src/trace_engine/plugin/__init__.py +157 -0
  181. package/src/trace_engine/plugin/__pycache__/__init__.cpython-311.pyc +0 -0
  182. package/src/trace_engine/plugin/__pycache__/evaluator.cpython-311.pyc +0 -0
  183. package/src/trace_engine/plugin/__pycache__/swebench_adapter.cpython-311.pyc +0 -0
  184. package/src/trace_engine/plugin/__pycache__/task.cpython-311.pyc +0 -0
  185. package/src/trace_engine/plugin/bundle/hooks.json +24 -0
  186. package/src/trace_engine/plugin/bundle/mcp_config.json +11 -0
  187. package/src/trace_engine/plugin/bundle/plugin.json +20 -0
  188. package/src/trace_engine/plugin/bundle/rules/security_remediation.md +40 -0
  189. package/src/trace_engine/plugin/bundle/skills/trace-security-harness/SKILL.md +118 -0
  190. package/src/trace_engine/plugin/evaluator.py +105 -0
  191. package/src/trace_engine/plugin/swebench_adapter.py +96 -0
  192. package/src/trace_engine/plugin/task.py +48 -0
  193. package/src/trace_engine/policy/__init__.py +5 -0
  194. package/src/trace_engine/policy/__pycache__/__init__.cpython-311.pyc +0 -0
  195. package/src/trace_engine/policy/__pycache__/scope.cpython-311.pyc +0 -0
  196. package/src/trace_engine/policy/scope.py +80 -0
  197. package/src/trace_engine/runtime/__init__.py +7 -0
  198. package/src/trace_engine/runtime/__pycache__/__init__.cpython-311.pyc +0 -0
  199. package/src/trace_engine/runtime/__pycache__/client.cpython-311.pyc +0 -0
  200. package/src/trace_engine/runtime/__pycache__/observations.cpython-311.pyc +0 -0
  201. package/src/trace_engine/runtime/__pycache__/target.cpython-311.pyc +0 -0
  202. package/src/trace_engine/runtime/client.py +81 -0
  203. package/src/trace_engine/runtime/observations.py +18 -0
  204. package/src/trace_engine/runtime/target.py +23 -0
  205. package/src/trace_engine/security/__init__.py +16 -0
  206. package/src/trace_engine/security/__pycache__/__init__.cpython-311.pyc +0 -0
  207. package/src/trace_engine/security/__pycache__/fusion.cpython-311.pyc +0 -0
  208. package/src/trace_engine/security/__pycache__/hypotheses.cpython-311.pyc +0 -0
  209. package/src/trace_engine/security/__pycache__/signals.cpython-311.pyc +0 -0
  210. package/src/trace_engine/security/__pycache__/timing.cpython-311.pyc +0 -0
  211. package/src/trace_engine/security/fusion.py +126 -0
  212. package/src/trace_engine/security/hypotheses.py +251 -0
  213. package/src/trace_engine/security/signals.py +28 -0
  214. package/src/trace_engine/security/timing.py +132 -0
  215. package/src/trace_engine/testpacks/__init__.py +18 -0
  216. package/src/trace_engine/testpacks/__pycache__/__init__.cpython-311.pyc +0 -0
  217. package/src/trace_engine/testpacks/__pycache__/authentication.cpython-311.pyc +0 -0
  218. package/src/trace_engine/testpacks/__pycache__/base.cpython-311.pyc +0 -0
  219. package/src/trace_engine/testpacks/__pycache__/bfla.cpython-311.pyc +0 -0
  220. package/src/trace_engine/testpacks/__pycache__/bola.cpython-311.pyc +0 -0
  221. package/src/trace_engine/testpacks/__pycache__/cors.cpython-311.pyc +0 -0
  222. package/src/trace_engine/testpacks/__pycache__/deserialization.cpython-311.pyc +0 -0
  223. package/src/trace_engine/testpacks/__pycache__/injection.cpython-311.pyc +0 -0
  224. package/src/trace_engine/testpacks/__pycache__/mass_assignment.cpython-311.pyc +0 -0
  225. package/src/trace_engine/testpacks/__pycache__/path_traversal.cpython-311.pyc +0 -0
  226. package/src/trace_engine/testpacks/__pycache__/registry.cpython-311.pyc +0 -0
  227. package/src/trace_engine/testpacks/__pycache__/ssrf.cpython-311.pyc +0 -0
  228. package/src/trace_engine/testpacks/__pycache__/ssti.cpython-311.pyc +0 -0
  229. package/src/trace_engine/testpacks/authentication.py +60 -0
  230. package/src/trace_engine/testpacks/base.py +63 -0
  231. package/src/trace_engine/testpacks/bfla.py +70 -0
  232. package/src/trace_engine/testpacks/bola.py +94 -0
  233. package/src/trace_engine/testpacks/cors.py +85 -0
  234. package/src/trace_engine/testpacks/deserialization.py +86 -0
  235. package/src/trace_engine/testpacks/injection.py +179 -0
  236. package/src/trace_engine/testpacks/mass_assignment.py +70 -0
  237. package/src/trace_engine/testpacks/path_traversal.py +117 -0
  238. package/src/trace_engine/testpacks/registry.py +44 -0
  239. package/src/trace_engine/testpacks/ssrf.py +85 -0
  240. package/src/trace_engine/testpacks/ssti.py +96 -0
  241. package/src/trace_engine/tools/__init__.py +6 -0
  242. package/src/trace_engine/tools/__pycache__/__init__.cpython-311.pyc +0 -0
  243. package/src/trace_engine/tools/__pycache__/adapters.cpython-311.pyc +0 -0
  244. package/src/trace_engine/tools/__pycache__/base.cpython-311.pyc +0 -0
  245. package/src/trace_engine/tools/__pycache__/registry.cpython-311.pyc +0 -0
  246. package/src/trace_engine/tools/adapters.py +111 -0
  247. package/src/trace_engine/tools/base.py +68 -0
  248. package/src/trace_engine/tools/registry.py +36 -0
  249. package/src/trace_engine/verify.py +156 -0
@@ -0,0 +1,465 @@
1
+ """Unified code parser for Python, JavaScript, and TypeScript."""
2
+
3
+ import ast
4
+ import re
5
+ from pathlib import Path
6
+ from typing import List, Optional, Dict, Any
7
+ from pydantic import BaseModel, Field
8
+
9
+ from trace_engine.parsing.locations import SourceLocation
10
+ from trace_engine.parsing.symbols import (
11
+ FunctionSymbol,
12
+ ClassSymbol,
13
+ ImportSymbol,
14
+ ParameterSymbol,
15
+ DecoratorSymbol,
16
+ )
17
+ from trace_engine.parsing.calls import CallSymbol
18
+ from trace_engine.parsing.language import get_tree_sitter_parser
19
+
20
+
21
+ class ParsedFile(BaseModel):
22
+ """Result of parsing a source file."""
23
+ file_path: str
24
+ absolute_path: Optional[str] = None
25
+ language: str
26
+ imports: List[ImportSymbol] = Field(default_factory=list)
27
+ classes: List[ClassSymbol] = Field(default_factory=list)
28
+ functions: List[FunctionSymbol] = Field(default_factory=list)
29
+ calls: List[CallSymbol] = Field(default_factory=list)
30
+
31
+
32
+ class CodeParser:
33
+ """Parses source files into structured AST symbols and calls."""
34
+
35
+ def parse(
36
+ self,
37
+ file_path: str,
38
+ content: str,
39
+ language: str,
40
+ absolute_path: Optional[str] = None,
41
+ ) -> ParsedFile:
42
+ if language == "python":
43
+ return self._parse_python(file_path, content, absolute_path=absolute_path)
44
+ elif language in ("javascript", "typescript"):
45
+ return self._parse_js_ts(file_path, content, language, absolute_path=absolute_path)
46
+ elif language == "dart":
47
+ return self._parse_dart(file_path, content, absolute_path=absolute_path)
48
+ elif language in ("java", "kotlin"):
49
+ return self._parse_jvm(file_path, content, language, absolute_path=absolute_path)
50
+ elif language == "go":
51
+ return self._parse_go(file_path, content, absolute_path=absolute_path)
52
+ else:
53
+ return ParsedFile(file_path=file_path, absolute_path=absolute_path, language=language)
54
+
55
+ def _parse_python(
56
+ self, file_path: str, content: str, absolute_path: Optional[str] = None
57
+ ) -> ParsedFile:
58
+ parsed = ParsedFile(file_path=file_path, absolute_path=absolute_path, language="python")
59
+ try:
60
+ tree = ast.parse(content, filename=file_path)
61
+ except Exception:
62
+ try:
63
+ import textwrap
64
+ tree = ast.parse(textwrap.dedent(content), filename=file_path)
65
+ except Exception:
66
+ return parsed
67
+
68
+ lines = content.splitlines()
69
+
70
+ for node in ast.walk(tree):
71
+ # Imports
72
+ if isinstance(node, ast.Import):
73
+ for alias in node.names:
74
+ parsed.imports.append(
75
+ ImportSymbol(
76
+ module=alias.name,
77
+ alias=alias.asname,
78
+ location=SourceLocation(
79
+ file=file_path,
80
+ line_start=node.lineno,
81
+ line_end=node.end_lineno or node.lineno,
82
+ ),
83
+ )
84
+ )
85
+ elif isinstance(node, ast.ImportFrom):
86
+ module = node.module or ""
87
+ names = [alias.name for alias in node.names]
88
+ parsed.imports.append(
89
+ ImportSymbol(
90
+ module=module,
91
+ imported_names=names,
92
+ location=SourceLocation(
93
+ file=file_path,
94
+ line_start=node.lineno,
95
+ line_end=node.end_lineno or node.lineno,
96
+ ),
97
+ )
98
+ )
99
+
100
+ # Classes and Top-level/Class Functions
101
+ for node in tree.body:
102
+ if isinstance(node, ast.ClassDef):
103
+ cls_symbol = self._extract_python_class(node, file_path, lines)
104
+ parsed.classes.append(cls_symbol)
105
+ for method in cls_symbol.methods:
106
+ parsed.functions.append(method)
107
+ elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
108
+ fn_symbol = self._extract_python_function(node, file_path, lines)
109
+ parsed.functions.append(fn_symbol)
110
+
111
+ # Extract calls inside functions
112
+ for fn in parsed.functions:
113
+ fn_calls = self._extract_python_calls_from_text(fn, lines, file_path)
114
+ parsed.calls.extend(fn_calls)
115
+
116
+ return parsed
117
+
118
+ def _extract_python_class(
119
+ self, node: ast.ClassDef, file_path: str, lines: List[str]
120
+ ) -> ClassSymbol:
121
+ bases = []
122
+ for b in node.bases:
123
+ if isinstance(b, ast.Name):
124
+ bases.append(b.id)
125
+ elif isinstance(b, ast.Attribute):
126
+ bases.append(f"{ast.unparse(b)}")
127
+
128
+ methods: List[FunctionSymbol] = []
129
+ for item in node.body:
130
+ if isinstance(item, (ast.FunctionDef, ast.AsyncFunctionDef)):
131
+ methods.append(
132
+ self._extract_python_function(
133
+ item, file_path, lines, parent_class=node.name
134
+ )
135
+ )
136
+
137
+ return ClassSymbol(
138
+ name=node.name,
139
+ base_classes=bases,
140
+ methods=methods,
141
+ location=SourceLocation(
142
+ file=file_path,
143
+ line_start=node.lineno,
144
+ line_end=node.end_lineno or node.lineno,
145
+ ),
146
+ )
147
+
148
+ def _extract_python_function(
149
+ self,
150
+ node: ast.AST,
151
+ file_path: str,
152
+ lines: List[str],
153
+ parent_class: Optional[str] = None,
154
+ ) -> FunctionSymbol:
155
+ is_async = isinstance(node, ast.AsyncFunctionDef)
156
+ name = getattr(node, "name", "unknown")
157
+ qualified_name = f"{parent_class}.{name}" if parent_class else name
158
+
159
+ # Parameters
160
+ params: List[ParameterSymbol] = []
161
+ args_node = getattr(node, "args", None)
162
+ if args_node:
163
+ for arg in args_node.args:
164
+ type_ann = ast.unparse(arg.annotation) if arg.annotation else None
165
+ params.append(
166
+ ParameterSymbol(
167
+ name=arg.arg,
168
+ type_annotation=type_ann,
169
+ location=SourceLocation(
170
+ file=file_path,
171
+ line_start=arg.lineno,
172
+ line_end=arg.end_lineno or arg.lineno,
173
+ ),
174
+ )
175
+ )
176
+
177
+ # Decorators
178
+ decorators: List[DecoratorSymbol] = []
179
+ for dec in getattr(node, "decorator_list", []):
180
+ try:
181
+ dec_text = ast.unparse(dec)
182
+ except Exception:
183
+ dec_text = ""
184
+ dec_name = dec_text.split("(")[0].strip()
185
+ args = []
186
+ if isinstance(dec, ast.Call):
187
+ args = [ast.unparse(a) for a in dec.args]
188
+ decorators.append(
189
+ DecoratorSymbol(
190
+ name=dec_name,
191
+ arguments=args,
192
+ raw_text=dec_text,
193
+ location=SourceLocation(
194
+ file=file_path,
195
+ line_start=dec.lineno,
196
+ line_end=dec.end_lineno or dec.lineno,
197
+ ),
198
+ )
199
+ )
200
+
201
+ # Body snippet
202
+ line_start = node.lineno
203
+ line_end = node.end_lineno or line_start
204
+ body_lines = lines[line_start - 1 : line_end]
205
+ body_text = "\n".join(body_lines)
206
+
207
+ return FunctionSymbol(
208
+ name=name,
209
+ qualified_name=qualified_name,
210
+ parameters=params,
211
+ decorators=decorators,
212
+ is_async=is_async,
213
+ location=SourceLocation(
214
+ file=file_path,
215
+ line_start=line_start,
216
+ line_end=line_end,
217
+ ),
218
+ body_text=body_text,
219
+ )
220
+
221
+ def _extract_python_calls_from_text(
222
+ self, fn: FunctionSymbol, lines: List[str], file_path: str
223
+ ) -> List[CallSymbol]:
224
+ calls: List[CallSymbol] = []
225
+ if not fn.body_text:
226
+ return calls
227
+
228
+ # Match function calls e.g. db.query(...), requests.get(...), auth.check(...)
229
+ pattern = re.compile(r"([a-zA-Z0-9_\.]+)\s*\((.*?)\)", re.DOTALL)
230
+ for match in pattern.finditer(fn.body_text):
231
+ callee = match.group(1)
232
+ # Filter language keywords
233
+ if callee in ("def", "class", "if", "for", "while", "return", "print"):
234
+ continue
235
+ args = [a.strip() for a in match.group(2).split(",") if a.strip()]
236
+ calls.append(
237
+ CallSymbol(
238
+ caller_name=fn.qualified_name,
239
+ callee_name=callee,
240
+ arguments=args[:5], # store first 5 args
241
+ location=fn.location,
242
+ )
243
+ )
244
+ return calls
245
+
246
+ def _parse_js_ts(
247
+ self, file_path: str, content: str, language: str, absolute_path: Optional[str] = None
248
+ ) -> ParsedFile:
249
+ parsed = ParsedFile(file_path=file_path, absolute_path=absolute_path, language=language)
250
+ lines = content.splitlines()
251
+
252
+ # Extract Imports: import ... from '...'; or require('...')
253
+ import_pat = re.compile(
254
+ r"""(?:import\s+.*?from\s+['"](.*?)['"]|require\s*\(\s*['"](.*?)['"]\s*\))"""
255
+ )
256
+ for idx, line in enumerate(lines, 1):
257
+ for match in import_pat.finditer(line):
258
+ mod = match.group(1) or match.group(2)
259
+ if mod:
260
+ parsed.imports.append(
261
+ ImportSymbol(
262
+ module=mod,
263
+ location=SourceLocation(
264
+ file=file_path, line_start=idx, line_end=idx
265
+ ),
266
+ )
267
+ )
268
+
269
+ # Extract Functions (standard function, arrow functions, function expressions)
270
+ fn_patterns = [
271
+ re.compile(r"""(?:async\s+)?function\s+([a-zA-Z0-9_$]+)\s*\((.*?)\)"""),
272
+ re.compile(r"""(?:const|let|var)\s+([a-zA-Z0-9_$]+)\s*=\s*(?:async\s*)?\((.*?)\)\s*=>"""),
273
+ re.compile(r"""(?:const|let|var)\s+([a-zA-Z0-9_$]+)\s*=\s*(?:async\s*)?function\s*\((.*?)\)"""),
274
+ ]
275
+ for idx, line in enumerate(lines, 1):
276
+ for pat in fn_patterns:
277
+ for match in pat.finditer(line):
278
+ name = match.group(1)
279
+ raw_args = match.group(2) or ""
280
+ args = [a.strip() for a in raw_args.split(",") if a.strip()]
281
+ params = [
282
+ ParameterSymbol(
283
+ name=arg.split(":")[0].strip(),
284
+ location=SourceLocation(
285
+ file=file_path, line_start=idx, line_end=idx
286
+ ),
287
+ )
288
+ for arg in args
289
+ ]
290
+ parsed.functions.append(
291
+ FunctionSymbol(
292
+ name=name,
293
+ qualified_name=name,
294
+ parameters=params,
295
+ location=SourceLocation(
296
+ file=file_path, line_start=idx, line_end=idx
297
+ ),
298
+ )
299
+ )
300
+
301
+ return parsed
302
+
303
+ def _parse_dart(
304
+ self, file_path: str, content: str, absolute_path: Optional[str] = None
305
+ ) -> ParsedFile:
306
+ parsed = ParsedFile(file_path=file_path, absolute_path=absolute_path, language="dart")
307
+ lines = content.splitlines()
308
+
309
+ # Imports: import 'package:...';
310
+ import_pat = re.compile(r"""import\s+['"](.*?)['"](?:\s+as\s+([a-zA-Z0-9_]+))?""")
311
+ for idx, line in enumerate(lines, 1):
312
+ for match in import_pat.finditer(line):
313
+ mod = match.group(1)
314
+ alias = match.group(2)
315
+ parsed.imports.append(
316
+ ImportSymbol(
317
+ module=mod,
318
+ alias=alias,
319
+ location=SourceLocation(file=file_path, line_start=idx, line_end=idx),
320
+ )
321
+ )
322
+
323
+ # Classes
324
+ cls_pat = re.compile(r"""class\s+([a-zA-Z0-9_$]+)""")
325
+ for idx, line in enumerate(lines, 1):
326
+ for match in cls_pat.finditer(line):
327
+ cls_name = match.group(1)
328
+ parsed.classes.append(
329
+ ClassSymbol(
330
+ name=cls_name,
331
+ location=SourceLocation(file=file_path, line_start=idx, line_end=idx),
332
+ )
333
+ )
334
+
335
+ # Functions / Handlers / Methods
336
+ fn_pat = re.compile(
337
+ r"""(?:(?:FutureOr|Future)<[^>]+>|[a-zA-Z0-9_<>]+)\s+([a-zA-Z0-9_$]+)\s*\((.*?)\)\s*(?:async\s*)?[{=]"""
338
+ )
339
+ for idx, line in enumerate(lines, 1):
340
+ for match in fn_pat.finditer(line):
341
+ name = match.group(1)
342
+ if name in ("if", "for", "while", "switch", "catch"):
343
+ continue
344
+ raw_args = match.group(2) or ""
345
+ args = [a.strip() for a in raw_args.split(",") if a.strip()]
346
+ params = [
347
+ ParameterSymbol(
348
+ name=arg.split()[-1].replace("?", ""),
349
+ location=SourceLocation(file=file_path, line_start=idx, line_end=idx),
350
+ )
351
+ for arg in args
352
+ ]
353
+ parsed.functions.append(
354
+ FunctionSymbol(
355
+ name=name,
356
+ qualified_name=name,
357
+ parameters=params,
358
+ location=SourceLocation(file=file_path, line_start=idx, line_end=idx),
359
+ )
360
+ )
361
+
362
+ return parsed
363
+
364
+ def _parse_jvm(
365
+ self, file_path: str, content: str, language: str, absolute_path: Optional[str] = None
366
+ ) -> ParsedFile:
367
+ parsed = ParsedFile(file_path=file_path, absolute_path=absolute_path, language=language)
368
+ lines = content.splitlines()
369
+
370
+ # Imports
371
+ import_pat = re.compile(r"""import\s+([a-zA-Z0-9_\.\*]+);?""")
372
+ for idx, line in enumerate(lines, 1):
373
+ for match in import_pat.finditer(line):
374
+ parsed.imports.append(
375
+ ImportSymbol(
376
+ module=match.group(1),
377
+ location=SourceLocation(file=file_path, line_start=idx, line_end=idx),
378
+ )
379
+ )
380
+
381
+ # Classes
382
+ cls_pat = re.compile(r"""(?:public\s+|private\s+|protected\s+|open\s+)?class\s+([a-zA-Z0-9_$]+)""")
383
+ for idx, line in enumerate(lines, 1):
384
+ for match in cls_pat.finditer(line):
385
+ parsed.classes.append(
386
+ ClassSymbol(
387
+ name=match.group(1),
388
+ location=SourceLocation(file=file_path, line_start=idx, line_end=idx),
389
+ )
390
+ )
391
+
392
+ # Functions (Kotlin 'fun' & Java methods)
393
+ if language == "kotlin":
394
+ fn_pat = re.compile(r"""fun\s+([a-zA-Z0-9_$]+)\s*\((.*?)\)""")
395
+ else:
396
+ fn_pat = re.compile(r"""(?:public|protected|private)?\s*(?:static\s+)?(?:final\s+)?(?:[a-zA-Z0-9_<>,\[\]]+)\s+([a-zA-Z0-9_$]+)\s*\((.*?)\)""")
397
+
398
+ for idx, line in enumerate(lines, 1):
399
+ for match in fn_pat.finditer(line):
400
+ name = match.group(1)
401
+ if name in ("if", "for", "while", "switch", "catch", "return", "class"):
402
+ continue
403
+ raw_args = match.group(2) or ""
404
+ args = [a.strip() for a in raw_args.split(",") if a.strip()]
405
+ params = [
406
+ ParameterSymbol(
407
+ name=arg.split()[-1] if language != "kotlin" else arg.split(":")[0].strip(),
408
+ location=SourceLocation(file=file_path, line_start=idx, line_end=idx),
409
+ )
410
+ for arg in args
411
+ ]
412
+ parsed.functions.append(
413
+ FunctionSymbol(
414
+ name=name,
415
+ qualified_name=name,
416
+ parameters=params,
417
+ location=SourceLocation(file=file_path, line_start=idx, line_end=idx),
418
+ )
419
+ )
420
+
421
+ return parsed
422
+
423
+ def _parse_go(
424
+ self, file_path: str, content: str, absolute_path: Optional[str] = None
425
+ ) -> ParsedFile:
426
+ parsed = ParsedFile(file_path=file_path, absolute_path=absolute_path, language="go")
427
+ lines = content.splitlines()
428
+
429
+ # Imports
430
+ import_pat = re.compile(r'''(?:import\s+['"](.*?)['"]|import\s*\((.*?)\))''', re.DOTALL)
431
+ for idx, line in enumerate(lines, 1):
432
+ m = re.search(r'''import\s+['"](.*?)['"]''', line)
433
+ if m:
434
+ parsed.imports.append(
435
+ ImportSymbol(
436
+ module=m.group(1),
437
+ location=SourceLocation(file=file_path, line_start=idx, line_end=idx),
438
+ )
439
+ )
440
+
441
+ # Functions
442
+ fn_pat = re.compile(r"""func\s+(?:\([^\)]+\)\s+)?([a-zA-Z0-9_]+)\s*\((.*?)\)""")
443
+ for idx, line in enumerate(lines, 1):
444
+ for match in fn_pat.finditer(line):
445
+ name = match.group(1)
446
+ raw_args = match.group(2) or ""
447
+ args = [a.strip() for a in raw_args.split(",") if a.strip()]
448
+ params = [
449
+ ParameterSymbol(
450
+ name=arg.split()[0],
451
+ location=SourceLocation(file=file_path, line_start=idx, line_end=idx),
452
+ )
453
+ for arg in args
454
+ ]
455
+ parsed.functions.append(
456
+ FunctionSymbol(
457
+ name=name,
458
+ qualified_name=name,
459
+ parameters=params,
460
+ location=SourceLocation(file=file_path, line_start=idx, line_end=idx),
461
+ )
462
+ )
463
+
464
+ return parsed
465
+
@@ -0,0 +1,45 @@
1
+ """Symbol definitions extracted from AST parsing."""
2
+
3
+ from typing import List, Optional, Dict, Any
4
+ from pydantic import BaseModel, Field
5
+ from trace_engine.parsing.locations import SourceLocation
6
+
7
+
8
+ class ParameterSymbol(BaseModel):
9
+ name: str
10
+ type_annotation: Optional[str] = None
11
+ default_value: Optional[str] = None
12
+ location: SourceLocation
13
+
14
+
15
+ class DecoratorSymbol(BaseModel):
16
+ name: str
17
+ arguments: List[str] = Field(default_factory=list)
18
+ raw_text: str
19
+ location: SourceLocation
20
+
21
+
22
+ class FunctionSymbol(BaseModel):
23
+ name: str
24
+ qualified_name: str
25
+ parameters: List[ParameterSymbol] = Field(default_factory=list)
26
+ decorators: List[DecoratorSymbol] = Field(default_factory=list)
27
+ docstring: Optional[str] = None
28
+ is_async: bool = False
29
+ return_type: Optional[str] = None
30
+ location: SourceLocation
31
+ body_text: Optional[str] = None
32
+
33
+
34
+ class ClassSymbol(BaseModel):
35
+ name: str
36
+ base_classes: List[str] = Field(default_factory=list)
37
+ methods: List[FunctionSymbol] = Field(default_factory=list)
38
+ location: SourceLocation
39
+
40
+
41
+ class ImportSymbol(BaseModel):
42
+ module: str
43
+ imported_names: List[str] = Field(default_factory=list)
44
+ alias: Optional[str] = None
45
+ location: SourceLocation
@@ -0,0 +1,157 @@
1
+ """TRACE Agent Harness Plugin - Core Interface.
2
+
3
+ Allows any AI coding agent harness (SWE-bench, Antigravity, OpenHands, Aider, Claude Code)
4
+ to plug in TRACE for autonomous vulnerability benchmark generation, attack-path modeling,
5
+ and deterministic verification.
6
+ """
7
+
8
+ import os
9
+ import shutil
10
+ import logging
11
+ from pathlib import Path
12
+ from typing import List, Optional, Dict, Any
13
+
14
+ from trace_engine.findings.store import FindingStore
15
+ from trace_engine.plugin.task import HarnessTaskSpec
16
+ from trace_engine.plugin.swebench_adapter import build_harness_task, export_swebench_jsonl
17
+ from trace_engine.plugin.evaluator import HarnessEvaluator, EvaluationResult
18
+
19
+ logger = logging.getLogger("trace_harness_plugin")
20
+
21
+
22
+ class TraceHarnessPlugin:
23
+ """Agent Harness Plugin interface for TRACE security evaluation and verification."""
24
+
25
+ PLUGIN_NAME = "trace-security"
26
+ VERSION = "2.0.0"
27
+
28
+ def __init__(self, repo_path: Optional[Path] = None, target_url: str = "http://127.0.0.1:18080"):
29
+ self.repo_path = Path(repo_path or ".").resolve()
30
+ self.target_url = target_url
31
+ self.finding_store = FindingStore(self.repo_path / ".trace")
32
+ self.evaluator = HarnessEvaluator(self.repo_path, target_url=self.target_url)
33
+
34
+ def list_tasks(self) -> List[HarnessTaskSpec]:
35
+ """Generate benchmark task instances for all correlated vulnerabilities found in the repo."""
36
+ findings = self.finding_store.load_findings()
37
+ tasks: List[HarnessTaskSpec] = []
38
+ for f in findings:
39
+ task = build_harness_task(f, self.repo_path)
40
+ tasks.append(task)
41
+ return tasks
42
+
43
+ def get_task(self, identifier: str) -> Optional[HarnessTaskSpec]:
44
+ """Retrieve task by instance ID or finding ID."""
45
+ for t in self.list_tasks():
46
+ if t.instance_id == identifier or t.finding_id == identifier:
47
+ return t
48
+ return None
49
+
50
+ def evaluate_patch(
51
+ self,
52
+ finding_id: str,
53
+ patch_content: Optional[str] = None,
54
+ target_file: Optional[str] = None,
55
+ rollback_after: bool = False,
56
+ ) -> EvaluationResult:
57
+ """Evaluate a patch produced by an external agent against the ground-truth verification oracle."""
58
+ return self.evaluator.evaluate_patch(
59
+ finding_id=finding_id,
60
+ patch_content=patch_content,
61
+ target_file=target_file,
62
+ rollback_after=rollback_after,
63
+ )
64
+
65
+ def export_dataset(self, output_path: Optional[Path] = None) -> Path:
66
+ """Export all benchmark tasks to standard SWE-bench JSONL format."""
67
+ out = output_path or (self.repo_path / "trace_bench_tasks.jsonl")
68
+ tasks = self.list_tasks()
69
+ export_swebench_jsonl(tasks, out)
70
+ return out
71
+
72
+ @staticmethod
73
+ def get_plugin_template_dir() -> Path:
74
+ """Locate the plugin template directory bundled with TRACE."""
75
+ # Check workspace first, then module directory
76
+ module_plugin = Path(__file__).resolve().parent / "bundle"
77
+ return module_plugin
78
+
79
+ def install(self, workspace_mode: bool = True, global_mode: bool = False) -> Dict[str, Path]:
80
+ """Installs the TRACE plugin into workspace .agents/plugins/ or global ~/.gemini/config/plugins/."""
81
+ installed_locations: Dict[str, Path] = {}
82
+
83
+ bundle_dir = self.get_plugin_template_dir()
84
+ if not bundle_dir.exists():
85
+ raise FileNotFoundError(f"Plugin template directory not found at {bundle_dir}")
86
+
87
+ if workspace_mode:
88
+ ws_dest = self.repo_path / ".agents" / "plugins" / self.PLUGIN_NAME
89
+ ws_dest.mkdir(parents=True, exist_ok=True)
90
+ self._copy_bundle(bundle_dir, ws_dest)
91
+ installed_locations["workspace"] = ws_dest
92
+
93
+ # Direct agent skill directory (.agents/skills/trace-security-harness/)
94
+ skill_src = bundle_dir / "skills" / "trace-security-harness"
95
+ if skill_src.exists():
96
+ skill_dest = self.repo_path / ".agents" / "skills" / "trace-security-harness"
97
+ skill_dest.mkdir(parents=True, exist_ok=True)
98
+ self._copy_bundle(skill_src, skill_dest)
99
+ installed_locations["agent_skill"] = skill_dest
100
+
101
+ # Claude Code skill integration (.claude/skills/trace-security/)
102
+ claude_skill_dest = self.repo_path / ".claude" / "skills" / "trace-security"
103
+ if skill_src.exists():
104
+ claude_skill_dest.mkdir(parents=True, exist_ok=True)
105
+ self._copy_bundle(skill_src, claude_skill_dest)
106
+ installed_locations["claude_skill"] = claude_skill_dest
107
+
108
+ # Generate or update .mcp.json for Claude Code and Cursor
109
+ from trace_engine.mcp.config import generate_mcp_json
110
+ try:
111
+ mcp_file = generate_mcp_json(self.repo_path)
112
+ installed_locations["mcp"] = mcp_file
113
+ # Cursor MCP configuration (.cursor/mcp.json)
114
+ cursor_mcp = self.repo_path / ".cursor" / "mcp.json"
115
+ cursor_mcp.parent.mkdir(parents=True, exist_ok=True)
116
+ shutil.copy2(mcp_file, cursor_mcp)
117
+ installed_locations["cursor_mcp"] = cursor_mcp
118
+ except Exception:
119
+ pass
120
+
121
+ if global_mode:
122
+ home = Path.home()
123
+ global_dest = home / ".gemini" / "config" / "plugins" / self.PLUGIN_NAME
124
+ global_dest.mkdir(parents=True, exist_ok=True)
125
+ self._copy_bundle(bundle_dir, global_dest)
126
+ installed_locations["global"] = global_dest
127
+
128
+ # Global Claude configuration (~/.claude/skills/trace-security/)
129
+ global_claude = home / ".claude" / "skills" / "trace-security"
130
+ skill_src = bundle_dir / "skills" / "trace-security-harness"
131
+ if skill_src.exists():
132
+ global_claude.mkdir(parents=True, exist_ok=True)
133
+ self._copy_bundle(skill_src, global_claude)
134
+ installed_locations["global_claude"] = global_claude
135
+
136
+ return installed_locations
137
+
138
+ def _copy_bundle(self, src: Path, dest: Path) -> None:
139
+ """Copy all files from plugin bundle into destination."""
140
+ for item in src.rglob("*"):
141
+ rel = item.relative_to(src)
142
+ target = dest / rel
143
+ if item.is_dir():
144
+ target.mkdir(parents=True, exist_ok=True)
145
+ else:
146
+ target.parent.mkdir(parents=True, exist_ok=True)
147
+ shutil.copy2(item, target)
148
+
149
+
150
+ __all__ = [
151
+ "TraceHarnessPlugin",
152
+ "HarnessTaskSpec",
153
+ "HarnessEvaluator",
154
+ "EvaluationResult",
155
+ "build_harness_task",
156
+ "export_swebench_jsonl",
157
+ ]