@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.
- package/README.md +208 -0
- package/bin/trace.js +112 -0
- package/package.json +46 -0
- package/pyproject.toml +39 -0
- package/src/trace_engine/__init__.py +8 -0
- package/src/trace_engine/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/__pycache__/cli.cpython-311.pyc +0 -0
- package/src/trace_engine/__pycache__/doctor.cpython-311.pyc +0 -0
- package/src/trace_engine/__pycache__/interactive.cpython-311.pyc +0 -0
- package/src/trace_engine/__pycache__/verify.cpython-311.pyc +0 -0
- package/src/trace_engine/ai/__init__.py +7 -0
- package/src/trace_engine/ai/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/ai/__pycache__/base.cpython-311.pyc +0 -0
- package/src/trace_engine/ai/__pycache__/ollama.cpython-311.pyc +0 -0
- package/src/trace_engine/ai/__pycache__/planner.cpython-311.pyc +0 -0
- package/src/trace_engine/ai/base.py +23 -0
- package/src/trace_engine/ai/ollama.py +50 -0
- package/src/trace_engine/ai/planner.py +40 -0
- package/src/trace_engine/apm/__init__.py +18 -0
- package/src/trace_engine/apm/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/apm/__pycache__/builder.cpython-311.pyc +0 -0
- package/src/trace_engine/apm/__pycache__/edges.cpython-311.pyc +0 -0
- package/src/trace_engine/apm/__pycache__/model.cpython-311.pyc +0 -0
- package/src/trace_engine/apm/__pycache__/nodes.cpython-311.pyc +0 -0
- package/src/trace_engine/apm/__pycache__/serialization.cpython-311.pyc +0 -0
- package/src/trace_engine/apm/builder.py +208 -0
- package/src/trace_engine/apm/edges.py +25 -0
- package/src/trace_engine/apm/model.py +107 -0
- package/src/trace_engine/apm/nodes.py +27 -0
- package/src/trace_engine/apm/serialization.py +105 -0
- package/src/trace_engine/benchmark/__init__.py +5 -0
- package/src/trace_engine/benchmark/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/benchmark/__pycache__/owasp.cpython-311.pyc +0 -0
- package/src/trace_engine/benchmark/owasp.py +183 -0
- package/src/trace_engine/cli.py +1184 -0
- package/src/trace_engine/config/__init__.py +35 -0
- package/src/trace_engine/config/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/config/__pycache__/defaults.cpython-311.pyc +0 -0
- package/src/trace_engine/config/__pycache__/loader.cpython-311.pyc +0 -0
- package/src/trace_engine/config/__pycache__/settings.cpython-311.pyc +0 -0
- package/src/trace_engine/config/defaults.py +48 -0
- package/src/trace_engine/config/loader.py +64 -0
- package/src/trace_engine/config/settings.py +72 -0
- package/src/trace_engine/doctor.py +250 -0
- package/src/trace_engine/findings/__init__.py +15 -0
- package/src/trace_engine/findings/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/findings/__pycache__/correlate.cpython-311.pyc +0 -0
- package/src/trace_engine/findings/__pycache__/model.cpython-311.pyc +0 -0
- package/src/trace_engine/findings/__pycache__/recommendations.cpython-311.pyc +0 -0
- package/src/trace_engine/findings/__pycache__/store.cpython-311.pyc +0 -0
- package/src/trace_engine/findings/correlate.py +103 -0
- package/src/trace_engine/findings/model.py +40 -0
- package/src/trace_engine/findings/recommendations.py +35 -0
- package/src/trace_engine/findings/store.py +39 -0
- package/src/trace_engine/framework/__init__.py +58 -0
- package/src/trace_engine/framework/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/base.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/csharp.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/dart.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/django.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/express.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/fastapi.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/flask.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/go.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/nextjs.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/php.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/react_router.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/ruby.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/rust.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/springboot.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/base.py +49 -0
- package/src/trace_engine/framework/csharp.py +111 -0
- package/src/trace_engine/framework/dart.py +152 -0
- package/src/trace_engine/framework/django.py +188 -0
- package/src/trace_engine/framework/express.py +82 -0
- package/src/trace_engine/framework/fastapi.py +135 -0
- package/src/trace_engine/framework/flask.py +108 -0
- package/src/trace_engine/framework/go.py +94 -0
- package/src/trace_engine/framework/nextjs.py +200 -0
- package/src/trace_engine/framework/php.py +98 -0
- package/src/trace_engine/framework/react_router.py +331 -0
- package/src/trace_engine/framework/ruby.py +69 -0
- package/src/trace_engine/framework/rust.py +101 -0
- package/src/trace_engine/framework/springboot.py +145 -0
- package/src/trace_engine/harness/__init__.py +19 -0
- package/src/trace_engine/harness/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/harness/__pycache__/benchmark.cpython-311.pyc +0 -0
- package/src/trace_engine/harness/__pycache__/context.cpython-311.pyc +0 -0
- package/src/trace_engine/harness/__pycache__/engine.cpython-311.pyc +0 -0
- package/src/trace_engine/harness/__pycache__/patcher.cpython-311.pyc +0 -0
- package/src/trace_engine/harness/__pycache__/remediators.cpython-311.pyc +0 -0
- package/src/trace_engine/harness/benchmark.py +55 -0
- package/src/trace_engine/harness/context.py +49 -0
- package/src/trace_engine/harness/engine.py +187 -0
- package/src/trace_engine/harness/patcher.py +143 -0
- package/src/trace_engine/harness/remediators.py +307 -0
- package/src/trace_engine/ingest/__init__.py +15 -0
- package/src/trace_engine/ingest/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/ingest/__pycache__/files.cpython-311.pyc +0 -0
- package/src/trace_engine/ingest/__pycache__/hashing.cpython-311.pyc +0 -0
- package/src/trace_engine/ingest/__pycache__/ignore.cpython-311.pyc +0 -0
- package/src/trace_engine/ingest/__pycache__/repository.cpython-311.pyc +0 -0
- package/src/trace_engine/ingest/files.py +60 -0
- package/src/trace_engine/ingest/hashing.py +21 -0
- package/src/trace_engine/ingest/ignore.py +108 -0
- package/src/trace_engine/ingest/repository.py +50 -0
- package/src/trace_engine/intelligence/__init__.py +15 -0
- package/src/trace_engine/intelligence/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/__pycache__/evaluation.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/__pycache__/orchestrator.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/__pycache__/tracebench.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/evaluation.py +828 -0
- package/src/trace_engine/intelligence/laya/__init__.py +19 -0
- package/src/trace_engine/intelligence/laya/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/laya/__pycache__/prompts.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/laya/__pycache__/router.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/laya/__pycache__/schemas.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/laya/__pycache__/telemetry.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/laya/__pycache__/thresholds.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/laya/prompts.py +67 -0
- package/src/trace_engine/intelligence/laya/router.py +352 -0
- package/src/trace_engine/intelligence/laya/schemas.py +56 -0
- package/src/trace_engine/intelligence/laya/telemetry.py +48 -0
- package/src/trace_engine/intelligence/laya/thresholds.py +12 -0
- package/src/trace_engine/intelligence/orchestrator.py +130 -0
- package/src/trace_engine/intelligence/securebert/__init__.py +6 -0
- package/src/trace_engine/intelligence/securebert/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/securebert/__pycache__/cache.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/securebert/__pycache__/classifier.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/securebert/cache.py +37 -0
- package/src/trace_engine/intelligence/securebert/classifier.py +240 -0
- package/src/trace_engine/intelligence/tracebench.py +61 -0
- package/src/trace_engine/intelligence/training/__init__.py +21 -0
- package/src/trace_engine/intelligence/training/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/training/__pycache__/dataset.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/training/__pycache__/dataset_importers.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/training/__pycache__/laya_trainer.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/training/__pycache__/lora_system2.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/training/__pycache__/morefixes_pipeline.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/training/__pycache__/slicer.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/training/__pycache__/train_all.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/training/__pycache__/trainer.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/training/dataset.py +320 -0
- package/src/trace_engine/intelligence/training/dataset_importers.py +349 -0
- package/src/trace_engine/intelligence/training/laya_trainer.py +678 -0
- package/src/trace_engine/intelligence/training/lora_system2.py +162 -0
- package/src/trace_engine/intelligence/training/morefixes_pipeline.py +463 -0
- package/src/trace_engine/intelligence/training/slicer.py +129 -0
- package/src/trace_engine/intelligence/training/train_all.py +1009 -0
- package/src/trace_engine/intelligence/training/trainer.py +321 -0
- package/src/trace_engine/interactive.py +623 -0
- package/src/trace_engine/mcp/__init__.py +6 -0
- package/src/trace_engine/mcp/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/mcp/__pycache__/config.cpython-311.pyc +0 -0
- package/src/trace_engine/mcp/__pycache__/server.cpython-311.pyc +0 -0
- package/src/trace_engine/mcp/config.py +42 -0
- package/src/trace_engine/mcp/server.py +398 -0
- package/src/trace_engine/output/__init__.py +28 -0
- package/src/trace_engine/output/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/output/__pycache__/html.cpython-311.pyc +0 -0
- package/src/trace_engine/output/__pycache__/markdown.cpython-311.pyc +0 -0
- package/src/trace_engine/output/__pycache__/sarif.cpython-311.pyc +0 -0
- package/src/trace_engine/output/__pycache__/terminal.cpython-311.pyc +0 -0
- package/src/trace_engine/output/html.py +142 -0
- package/src/trace_engine/output/markdown.py +65 -0
- package/src/trace_engine/output/sarif.py +180 -0
- package/src/trace_engine/output/terminal.py +537 -0
- package/src/trace_engine/parsing/__init__.py +26 -0
- package/src/trace_engine/parsing/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/parsing/__pycache__/calls.cpython-311.pyc +0 -0
- package/src/trace_engine/parsing/__pycache__/language.cpython-311.pyc +0 -0
- package/src/trace_engine/parsing/__pycache__/locations.cpython-311.pyc +0 -0
- package/src/trace_engine/parsing/__pycache__/parser.cpython-311.pyc +0 -0
- package/src/trace_engine/parsing/__pycache__/symbols.cpython-311.pyc +0 -0
- package/src/trace_engine/parsing/calls.py +14 -0
- package/src/trace_engine/parsing/language.py +24 -0
- package/src/trace_engine/parsing/locations.py +17 -0
- package/src/trace_engine/parsing/parser.py +465 -0
- package/src/trace_engine/parsing/symbols.py +45 -0
- package/src/trace_engine/plugin/__init__.py +157 -0
- package/src/trace_engine/plugin/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/plugin/__pycache__/evaluator.cpython-311.pyc +0 -0
- package/src/trace_engine/plugin/__pycache__/swebench_adapter.cpython-311.pyc +0 -0
- package/src/trace_engine/plugin/__pycache__/task.cpython-311.pyc +0 -0
- package/src/trace_engine/plugin/bundle/hooks.json +24 -0
- package/src/trace_engine/plugin/bundle/mcp_config.json +11 -0
- package/src/trace_engine/plugin/bundle/plugin.json +20 -0
- package/src/trace_engine/plugin/bundle/rules/security_remediation.md +40 -0
- package/src/trace_engine/plugin/bundle/skills/trace-security-harness/SKILL.md +118 -0
- package/src/trace_engine/plugin/evaluator.py +105 -0
- package/src/trace_engine/plugin/swebench_adapter.py +96 -0
- package/src/trace_engine/plugin/task.py +48 -0
- package/src/trace_engine/policy/__init__.py +5 -0
- package/src/trace_engine/policy/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/policy/__pycache__/scope.cpython-311.pyc +0 -0
- package/src/trace_engine/policy/scope.py +80 -0
- package/src/trace_engine/runtime/__init__.py +7 -0
- package/src/trace_engine/runtime/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/runtime/__pycache__/client.cpython-311.pyc +0 -0
- package/src/trace_engine/runtime/__pycache__/observations.cpython-311.pyc +0 -0
- package/src/trace_engine/runtime/__pycache__/target.cpython-311.pyc +0 -0
- package/src/trace_engine/runtime/client.py +81 -0
- package/src/trace_engine/runtime/observations.py +18 -0
- package/src/trace_engine/runtime/target.py +23 -0
- package/src/trace_engine/security/__init__.py +16 -0
- package/src/trace_engine/security/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/security/__pycache__/fusion.cpython-311.pyc +0 -0
- package/src/trace_engine/security/__pycache__/hypotheses.cpython-311.pyc +0 -0
- package/src/trace_engine/security/__pycache__/signals.cpython-311.pyc +0 -0
- package/src/trace_engine/security/__pycache__/timing.cpython-311.pyc +0 -0
- package/src/trace_engine/security/fusion.py +126 -0
- package/src/trace_engine/security/hypotheses.py +251 -0
- package/src/trace_engine/security/signals.py +28 -0
- package/src/trace_engine/security/timing.py +132 -0
- package/src/trace_engine/testpacks/__init__.py +18 -0
- package/src/trace_engine/testpacks/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/authentication.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/base.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/bfla.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/bola.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/cors.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/deserialization.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/injection.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/mass_assignment.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/path_traversal.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/registry.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/ssrf.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/ssti.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/authentication.py +60 -0
- package/src/trace_engine/testpacks/base.py +63 -0
- package/src/trace_engine/testpacks/bfla.py +70 -0
- package/src/trace_engine/testpacks/bola.py +94 -0
- package/src/trace_engine/testpacks/cors.py +85 -0
- package/src/trace_engine/testpacks/deserialization.py +86 -0
- package/src/trace_engine/testpacks/injection.py +179 -0
- package/src/trace_engine/testpacks/mass_assignment.py +70 -0
- package/src/trace_engine/testpacks/path_traversal.py +117 -0
- package/src/trace_engine/testpacks/registry.py +44 -0
- package/src/trace_engine/testpacks/ssrf.py +85 -0
- package/src/trace_engine/testpacks/ssti.py +96 -0
- package/src/trace_engine/tools/__init__.py +6 -0
- package/src/trace_engine/tools/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/tools/__pycache__/adapters.cpython-311.pyc +0 -0
- package/src/trace_engine/tools/__pycache__/base.cpython-311.pyc +0 -0
- package/src/trace_engine/tools/__pycache__/registry.cpython-311.pyc +0 -0
- package/src/trace_engine/tools/adapters.py +111 -0
- package/src/trace_engine/tools/base.py +68 -0
- package/src/trace_engine/tools/registry.py +36 -0
- 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
|
+
]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|