atomicguard 0.1.0__py3-none-any.whl → 1.2.0__py3-none-any.whl
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.
- atomicguard/__init__.py +8 -3
- atomicguard/application/action_pair.py +7 -1
- atomicguard/application/agent.py +46 -6
- atomicguard/application/workflow.py +494 -11
- atomicguard/domain/__init__.py +4 -1
- atomicguard/domain/exceptions.py +19 -0
- atomicguard/domain/interfaces.py +137 -6
- atomicguard/domain/models.py +120 -6
- atomicguard/guards/__init__.py +16 -5
- atomicguard/guards/composite/__init__.py +11 -0
- atomicguard/guards/dynamic/__init__.py +13 -0
- atomicguard/guards/dynamic/test_runner.py +207 -0
- atomicguard/guards/interactive/__init__.py +11 -0
- atomicguard/guards/static/__init__.py +13 -0
- atomicguard/guards/static/imports.py +177 -0
- atomicguard/infrastructure/__init__.py +4 -1
- atomicguard/infrastructure/llm/__init__.py +3 -1
- atomicguard/infrastructure/llm/huggingface.py +180 -0
- atomicguard/infrastructure/llm/mock.py +32 -6
- atomicguard/infrastructure/llm/ollama.py +40 -17
- atomicguard/infrastructure/persistence/__init__.py +7 -1
- atomicguard/infrastructure/persistence/checkpoint.py +361 -0
- atomicguard/infrastructure/persistence/filesystem.py +69 -5
- atomicguard/infrastructure/persistence/memory.py +25 -3
- atomicguard/infrastructure/registry.py +126 -0
- atomicguard/schemas/__init__.py +142 -0
- {atomicguard-0.1.0.dist-info → atomicguard-1.2.0.dist-info}/METADATA +75 -13
- atomicguard-1.2.0.dist-info/RECORD +37 -0
- {atomicguard-0.1.0.dist-info → atomicguard-1.2.0.dist-info}/WHEEL +1 -1
- atomicguard-1.2.0.dist-info/entry_points.txt +4 -0
- atomicguard/guards/test_runner.py +0 -176
- atomicguard-0.1.0.dist-info/RECORD +0 -27
- /atomicguard/guards/{base.py → composite/base.py} +0 -0
- /atomicguard/guards/{human.py → interactive/human.py} +0 -0
- /atomicguard/guards/{syntax.py → static/syntax.py} +0 -0
- {atomicguard-0.1.0.dist-info → atomicguard-1.2.0.dist-info}/licenses/LICENSE +0 -0
- {atomicguard-0.1.0.dist-info → atomicguard-1.2.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""AtomicGuard JSON Schema definitions and validation utilities.
|
|
2
|
+
|
|
3
|
+
This module provides JSON Schema definitions for AtomicGuard configuration files,
|
|
4
|
+
aligned with the formal framework defined in the paper.
|
|
5
|
+
|
|
6
|
+
Schemas:
|
|
7
|
+
- workflow.schema.json: Workflow definition (action pairs, guards, preconditions)
|
|
8
|
+
- prompts.schema.json: Prompt templates for generators
|
|
9
|
+
- artifact.schema.json: Artifact storage format in DAG
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
from atomicguard.schemas import validate_workflow, validate_prompts
|
|
13
|
+
|
|
14
|
+
# Validate a workflow configuration
|
|
15
|
+
with open("workflow.json") as f:
|
|
16
|
+
data = json.load(f)
|
|
17
|
+
validate_workflow(data) # Raises jsonschema.ValidationError if invalid
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import json
|
|
23
|
+
from importlib.resources import files
|
|
24
|
+
from typing import TYPE_CHECKING, Any
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
import types
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
import jsonschema as _jsonschema
|
|
31
|
+
|
|
32
|
+
jsonschema: types.ModuleType | None = _jsonschema
|
|
33
|
+
except ImportError:
|
|
34
|
+
jsonschema = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _load_schema(name: str) -> dict[str, Any]:
|
|
38
|
+
"""Load a JSON schema from the schemas package.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
name: Schema filename (e.g., 'workflow.schema.json')
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Parsed JSON schema as a dictionary
|
|
45
|
+
"""
|
|
46
|
+
schema_text = files("atomicguard.schemas").joinpath(name).read_text()
|
|
47
|
+
result: dict[str, Any] = json.loads(schema_text)
|
|
48
|
+
return result
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get_workflow_schema() -> dict[str, Any]:
|
|
52
|
+
"""Get the workflow.json schema.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
JSON Schema for workflow configuration
|
|
56
|
+
"""
|
|
57
|
+
return _load_schema("workflow.schema.json")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def get_prompts_schema() -> dict[str, Any]:
|
|
61
|
+
"""Get the prompts.json schema.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
JSON Schema for prompt templates
|
|
65
|
+
"""
|
|
66
|
+
return _load_schema("prompts.schema.json")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def get_artifact_schema() -> dict[str, Any]:
|
|
70
|
+
"""Get the artifact.json schema.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
JSON Schema for artifact storage
|
|
74
|
+
"""
|
|
75
|
+
return _load_schema("artifact.schema.json")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def validate_workflow(data: dict[str, Any]) -> None:
|
|
79
|
+
"""Validate a workflow configuration against the schema.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
data: Workflow configuration dictionary
|
|
83
|
+
|
|
84
|
+
Raises:
|
|
85
|
+
jsonschema.ValidationError: If validation fails
|
|
86
|
+
ImportError: If jsonschema is not installed
|
|
87
|
+
"""
|
|
88
|
+
if jsonschema is None:
|
|
89
|
+
raise ImportError(
|
|
90
|
+
"jsonschema is required for validation. "
|
|
91
|
+
"Install it with: pip install jsonschema"
|
|
92
|
+
)
|
|
93
|
+
schema = get_workflow_schema()
|
|
94
|
+
jsonschema.validate(data, schema)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def validate_prompts(data: dict[str, Any]) -> None:
|
|
98
|
+
"""Validate prompt templates against the schema.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
data: Prompts configuration dictionary
|
|
102
|
+
|
|
103
|
+
Raises:
|
|
104
|
+
jsonschema.ValidationError: If validation fails
|
|
105
|
+
ImportError: If jsonschema is not installed
|
|
106
|
+
"""
|
|
107
|
+
if jsonschema is None:
|
|
108
|
+
raise ImportError(
|
|
109
|
+
"jsonschema is required for validation. "
|
|
110
|
+
"Install it with: pip install jsonschema"
|
|
111
|
+
)
|
|
112
|
+
schema = get_prompts_schema()
|
|
113
|
+
jsonschema.validate(data, schema)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def validate_artifact(data: dict[str, Any]) -> None:
|
|
117
|
+
"""Validate an artifact against the schema.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
data: Artifact dictionary
|
|
121
|
+
|
|
122
|
+
Raises:
|
|
123
|
+
jsonschema.ValidationError: If validation fails
|
|
124
|
+
ImportError: If jsonschema is not installed
|
|
125
|
+
"""
|
|
126
|
+
if jsonschema is None:
|
|
127
|
+
raise ImportError(
|
|
128
|
+
"jsonschema is required for validation. "
|
|
129
|
+
"Install it with: pip install jsonschema"
|
|
130
|
+
)
|
|
131
|
+
schema = get_artifact_schema()
|
|
132
|
+
jsonschema.validate(data, schema)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
__all__ = [
|
|
136
|
+
"get_workflow_schema",
|
|
137
|
+
"get_prompts_schema",
|
|
138
|
+
"get_artifact_schema",
|
|
139
|
+
"validate_workflow",
|
|
140
|
+
"validate_prompts",
|
|
141
|
+
"validate_artifact",
|
|
142
|
+
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: atomicguard
|
|
3
|
-
Version:
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: A Dual-State Agent Framework for reliable LLM code generation with guard-validated loops
|
|
5
5
|
Author-email: Matthew Thompson <thompsonson@gmail.com>
|
|
6
6
|
Maintainer-email: Matthew Thompson <thompsonson@gmail.com>
|
|
@@ -26,17 +26,10 @@ Classifier: Typing :: Typed
|
|
|
26
26
|
Requires-Python: >=3.12
|
|
27
27
|
Description-Content-Type: text/markdown
|
|
28
28
|
License-File: LICENSE
|
|
29
|
-
Requires-Dist: click>=8.3.1
|
|
30
29
|
Requires-Dist: matplotlib>=3.10.0
|
|
31
|
-
Requires-Dist:
|
|
32
|
-
Requires-Dist:
|
|
33
|
-
|
|
34
|
-
Requires-Dist: mypy>=1.13.0; extra == "dev"
|
|
35
|
-
Requires-Dist: pre-commit>=4.5.0; extra == "dev"
|
|
36
|
-
Requires-Dist: ruff>=0.14.0; extra == "dev"
|
|
37
|
-
Provides-Extra: test
|
|
38
|
-
Requires-Dist: pytest>=8.0.0; extra == "test"
|
|
39
|
-
Requires-Dist: pytest-cov>=6.0.0; extra == "test"
|
|
30
|
+
Requires-Dist: openhands-ai>=0.27.0
|
|
31
|
+
Requires-Dist: pydantic-ai>=1.0.0
|
|
32
|
+
Requires-Dist: pytestarch>=4.0.1
|
|
40
33
|
Dynamic: license-file
|
|
41
34
|
|
|
42
35
|
# AtomicGuard
|
|
@@ -100,6 +93,67 @@ print(artifact.content)
|
|
|
100
93
|
|
|
101
94
|
See [examples/](examples/) for more detailed usage, including a [mock example](examples/basic_mock.py) that works without an LLM.
|
|
102
95
|
|
|
96
|
+
## LLM Backends
|
|
97
|
+
|
|
98
|
+
AtomicGuard supports multiple LLM backends. Each generator implements `GeneratorInterface` and can be swapped in with no other code changes.
|
|
99
|
+
|
|
100
|
+
### Ollama (local or cloud)
|
|
101
|
+
|
|
102
|
+
Uses the OpenAI-compatible API. Works with any Ollama-served model:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from atomicguard.infrastructure.llm import OllamaGenerator
|
|
106
|
+
|
|
107
|
+
# Local instance (default: http://localhost:11434/v1)
|
|
108
|
+
generator = OllamaGenerator(model="qwen2.5-coder:7b")
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### HuggingFace Inference API
|
|
112
|
+
|
|
113
|
+
Connects to HuggingFace Inference Providers via `huggingface_hub`. Supports any model available through the HF Inference API, including third-party providers like Together AI.
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
# Install the optional dependency
|
|
117
|
+
pip install huggingface_hub
|
|
118
|
+
|
|
119
|
+
# Set your API token
|
|
120
|
+
export HF_TOKEN="hf_your_token_here"
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from atomicguard.infrastructure.llm import HuggingFaceGenerator
|
|
125
|
+
from atomicguard.infrastructure.llm.huggingface import HuggingFaceGeneratorConfig
|
|
126
|
+
|
|
127
|
+
# Default: Qwen/Qwen2.5-Coder-32B-Instruct
|
|
128
|
+
generator = HuggingFaceGenerator()
|
|
129
|
+
|
|
130
|
+
# Custom model and provider
|
|
131
|
+
generator = HuggingFaceGenerator(HuggingFaceGeneratorConfig(
|
|
132
|
+
model="Qwen/Qwen2.5-Coder-32B-Instruct",
|
|
133
|
+
provider="together", # or "auto", "hf-inference"
|
|
134
|
+
temperature=0.7,
|
|
135
|
+
max_tokens=4096,
|
|
136
|
+
))
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Drop-in replacement in any workflow:
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
from atomicguard import (
|
|
143
|
+
SyntaxGuard, TestGuard, CompositeGuard,
|
|
144
|
+
ActionPair, DualStateAgent, InMemoryArtifactDAG
|
|
145
|
+
)
|
|
146
|
+
from atomicguard.infrastructure.llm import HuggingFaceGenerator
|
|
147
|
+
|
|
148
|
+
generator = HuggingFaceGenerator()
|
|
149
|
+
guard = CompositeGuard([SyntaxGuard(), TestGuard("assert add(2, 3) == 5")])
|
|
150
|
+
action_pair = ActionPair(generator=generator, guard=guard)
|
|
151
|
+
agent = DualStateAgent(action_pair, InMemoryArtifactDAG(), rmax=3)
|
|
152
|
+
|
|
153
|
+
artifact = agent.execute("Write a function that adds two numbers")
|
|
154
|
+
print(artifact.content)
|
|
155
|
+
```
|
|
156
|
+
|
|
103
157
|
## Benchmarks
|
|
104
158
|
|
|
105
159
|
Run the simulation from the paper:
|
|
@@ -124,11 +178,19 @@ atomicguard/
|
|
|
124
178
|
|
|
125
179
|
## Citation
|
|
126
180
|
|
|
181
|
+
If you use this framework in your research, please cite the paper:
|
|
182
|
+
|
|
183
|
+
> Thompson, M. (2025). Managing the Stochastic: Foundations of Learning in Neuro-Symbolic Systems for Software Engineering. arXiv preprint arXiv:2512.20660.
|
|
184
|
+
|
|
127
185
|
```bibtex
|
|
128
|
-
@
|
|
186
|
+
@misc{thompson2025managing,
|
|
129
187
|
title={Managing the Stochastic: Foundations of Learning in Neuro-Symbolic Systems for Software Engineering},
|
|
130
188
|
author={Thompson, Matthew},
|
|
131
|
-
year={2025}
|
|
189
|
+
year={2025},
|
|
190
|
+
eprint={2512.20660},
|
|
191
|
+
archivePrefix={arXiv},
|
|
192
|
+
primaryClass={cs.LG},
|
|
193
|
+
url={https://arxiv.org/abs/2512.20660}
|
|
132
194
|
}
|
|
133
195
|
```
|
|
134
196
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
atomicguard/__init__.py,sha256=vW_pYf3hdmq4rgIYWc0319HMUAB-JkeraoqEdFIe--U,3051
|
|
2
|
+
atomicguard/application/__init__.py,sha256=-myJt7VuC1tMjO424NhjY6iZZuUdHGnFCJKFapFCAhw,409
|
|
3
|
+
atomicguard/application/action_pair.py,sha256=JfQH88gTZCMl5qa5frqLJnhebn-KgO_oGAol6sD58ys,2214
|
|
4
|
+
atomicguard/application/agent.py,sha256=ND2_pnRGwcWM5MovM8dnratrMph2gz9UmwXEgTeA59s,5907
|
|
5
|
+
atomicguard/application/workflow.py,sha256=mfWz2pdBqQsGC5rBZ5S1x_jaNe2aLvqoG20TvWyAsnM,23309
|
|
6
|
+
atomicguard/domain/__init__.py,sha256=BRmYKYFU-kLG1wPpRrXKxZ2jb4QgMea83r0gbDX_Da0,1149
|
|
7
|
+
atomicguard/domain/exceptions.py,sha256=Zj1iz31AiksyWnyKR8KfnNLRGhZdetrYhCIwFOapBtY,1376
|
|
8
|
+
atomicguard/domain/interfaces.py,sha256=73vRiPzTLkVAiBc2srn-EUNFSQfleooQNx6r3odIQFM,6574
|
|
9
|
+
atomicguard/domain/models.py,sha256=sVuHzuP98pgHefTz1mk1bYptgoeAySekRyBG2zMz7TE,8496
|
|
10
|
+
atomicguard/domain/prompts.py,sha256=SMGcP0kYc9azCbGku5EG0JZZs83D80WkJO-DWiuFV58,2785
|
|
11
|
+
atomicguard/guards/__init__.py,sha256=OzpOjwmyHGpTQSDVSDeGEuXUJNOyiNpZxZVnTVMIOyg,956
|
|
12
|
+
atomicguard/guards/composite/__init__.py,sha256=AedlMBxFw4uTFExVWL6RJRR7vRwMOK3sgjhutkXz838,217
|
|
13
|
+
atomicguard/guards/composite/base.py,sha256=VGydlOD4gmXdiDHvWXF5jSppXcJAoJYSCyDj7Lrbng0,1213
|
|
14
|
+
atomicguard/guards/dynamic/__init__.py,sha256=pjBBeD_yYavvn-I7s5I3nTDOlZtPTq1Hqmim_RgW5cE,321
|
|
15
|
+
atomicguard/guards/dynamic/test_runner.py,sha256=d5cPb5cCM-UnfZjTYGOHn4XoS-N66nr5EBHqPoDGwNM,7196
|
|
16
|
+
atomicguard/guards/interactive/__init__.py,sha256=PlP5LNB4CMZ4fJ7Xr8gjjpn8VwLnB3zs5M2Em9xyVCc,226
|
|
17
|
+
atomicguard/guards/interactive/human.py,sha256=G8SstAEdcMpHR2b8ugyG9drk9aBjw4_pE6E4UrTtcUo,2960
|
|
18
|
+
atomicguard/guards/static/__init__.py,sha256=KANeEg_yhrKGzSmA0yf2SewOE9qkUwnJly4mWkBzsm4,305
|
|
19
|
+
atomicguard/guards/static/imports.py,sha256=nWS3FNNqle7YWU5_tgRgYMRAXwEcWlxee8Q4MdomN5w,6292
|
|
20
|
+
atomicguard/guards/static/syntax.py,sha256=mPVgGDY3pzwtXuulmmuEwYAQG7rNG0fgSGB1puYRI6Y,919
|
|
21
|
+
atomicguard/infrastructure/__init__.py,sha256=Y3Ot6sRgTind-8s5kCnNtRYJGN3uq84QmbAUESx8Pio,581
|
|
22
|
+
atomicguard/infrastructure/registry.py,sha256=XilDY2sUedwm5z1Xrp39HfQ9e3paixyJxL_hwGaVbqs,3662
|
|
23
|
+
atomicguard/infrastructure/llm/__init__.py,sha256=wQg_FCAHmwbDXWm0bF5WX-9v7sutAS7RabhHCGiZZzo,338
|
|
24
|
+
atomicguard/infrastructure/llm/huggingface.py,sha256=8VpkjuIzxAKOj3C6OBufS394AuDtbhreuPNv1rm-qJg,5756
|
|
25
|
+
atomicguard/infrastructure/llm/mock.py,sha256=883Y7e2Q1yWapvp3rXn03bHEvuRnFwDe0CZsTZziG_E,2639
|
|
26
|
+
atomicguard/infrastructure/llm/ollama.py,sha256=pLSZX_X5U1aPvIxCsF_wsQ_AHsTspqU3xkYHWLbgJQ0,4832
|
|
27
|
+
atomicguard/infrastructure/persistence/__init__.py,sha256=KS95wmO_qVF6It63_884lWDoa-IB0WvRma_nXS2uTq4,483
|
|
28
|
+
atomicguard/infrastructure/persistence/checkpoint.py,sha256=MS4qpJ_jcl6Ibw7h3KncWgDqz7CEIlFJdrpFcu2vEgs,13966
|
|
29
|
+
atomicguard/infrastructure/persistence/filesystem.py,sha256=4upoycWCOlWRoSlVm4Ux_Kj8r3hX3Nm8N80x-IfBSLw,11440
|
|
30
|
+
atomicguard/infrastructure/persistence/memory.py,sha256=JTnTwXSKYYDnQEYbjwAFVpklZVAlQlCDIt9zxcQjlz0,2042
|
|
31
|
+
atomicguard/schemas/__init__.py,sha256=9z3gWBHsq8FHD2qE_TPy31wkTa_qXPfVgJrNdIu8p4g,3735
|
|
32
|
+
atomicguard-1.2.0.dist-info/licenses/LICENSE,sha256=ROMMFVruZ18U24pKTNte9AK_YIqoMfXnMzoxqBNmKS4,1073
|
|
33
|
+
atomicguard-1.2.0.dist-info/METADATA,sha256=slCf2YR0p3mYoZR2XnP96NUdCiD-qr5tLgMGo-LU4jk,6831
|
|
34
|
+
atomicguard-1.2.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
35
|
+
atomicguard-1.2.0.dist-info/entry_points.txt,sha256=MMVrkEyWFFCMoNIlpSWRY9FJYlEMGmaauvzMxEjwzTI,226
|
|
36
|
+
atomicguard-1.2.0.dist-info/top_level.txt,sha256=J_6ENELjnacSYJ5N3FGwomp-sVeAJQohPkJBh6pu6iY,12
|
|
37
|
+
atomicguard-1.2.0.dist-info/RECORD,,
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Test execution guards.
|
|
3
|
-
|
|
4
|
-
Guards that validate artifacts by running tests against them.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import multiprocessing
|
|
8
|
-
import sys
|
|
9
|
-
import types
|
|
10
|
-
from typing import Any
|
|
11
|
-
|
|
12
|
-
from atomicguard.domain.interfaces import GuardInterface
|
|
13
|
-
from atomicguard.domain.models import Artifact, GuardResult
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class TestGuard(GuardInterface):
|
|
17
|
-
"""
|
|
18
|
-
Validates artifact via test execution in the same process.
|
|
19
|
-
|
|
20
|
-
Simple guard that executes test code against artifact content.
|
|
21
|
-
For isolation, use DynamicTestGuard instead.
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
def __init__(self, test_code: str | None = None):
|
|
25
|
-
"""
|
|
26
|
-
Args:
|
|
27
|
-
test_code: Static test code to run (if not using dependencies)
|
|
28
|
-
"""
|
|
29
|
-
self._static_test_code = test_code
|
|
30
|
-
|
|
31
|
-
def validate(self, artifact: Artifact, **deps: Any) -> GuardResult:
|
|
32
|
-
"""
|
|
33
|
-
Execute test code against artifact.
|
|
34
|
-
|
|
35
|
-
Args:
|
|
36
|
-
artifact: The implementation artifact to test
|
|
37
|
-
**deps: May include 'test' artifact with test code
|
|
38
|
-
|
|
39
|
-
Returns:
|
|
40
|
-
GuardResult with test outcome
|
|
41
|
-
"""
|
|
42
|
-
test_artifact = deps.get("test")
|
|
43
|
-
test_code = test_artifact.content if test_artifact else self._static_test_code
|
|
44
|
-
|
|
45
|
-
if not test_code:
|
|
46
|
-
return GuardResult(passed=False, feedback="No test code provided")
|
|
47
|
-
|
|
48
|
-
namespace: dict[str, Any] = {}
|
|
49
|
-
try:
|
|
50
|
-
exec(artifact.content, namespace)
|
|
51
|
-
exec(test_code, namespace)
|
|
52
|
-
return GuardResult(passed=True)
|
|
53
|
-
except AssertionError as e:
|
|
54
|
-
return GuardResult(passed=False, feedback=f"Test failed: {e}")
|
|
55
|
-
except Exception as e:
|
|
56
|
-
return GuardResult(passed=False, feedback=f"{type(e).__name__}: {e}")
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
class DynamicTestGuard(GuardInterface):
|
|
60
|
-
"""
|
|
61
|
-
Runs test code against implementation in isolated subprocess.
|
|
62
|
-
|
|
63
|
-
Expects 'test' dependency containing the test artifact.
|
|
64
|
-
Executes tests and returns pass/fail with detailed feedback.
|
|
65
|
-
|
|
66
|
-
Uses multiprocessing for isolation to prevent test code from
|
|
67
|
-
affecting the parent process.
|
|
68
|
-
"""
|
|
69
|
-
|
|
70
|
-
def __init__(self, timeout: float = 60.0):
|
|
71
|
-
"""
|
|
72
|
-
Args:
|
|
73
|
-
timeout: Maximum time in seconds to wait for test execution
|
|
74
|
-
"""
|
|
75
|
-
self.timeout = timeout
|
|
76
|
-
|
|
77
|
-
def validate(self, artifact: Artifact, **deps: Any) -> GuardResult:
|
|
78
|
-
"""
|
|
79
|
-
Run tests in isolated subprocess.
|
|
80
|
-
|
|
81
|
-
Args:
|
|
82
|
-
artifact: The implementation artifact to test
|
|
83
|
-
**deps: Must include 'test' artifact with test code
|
|
84
|
-
|
|
85
|
-
Returns:
|
|
86
|
-
GuardResult with test outcome
|
|
87
|
-
"""
|
|
88
|
-
test_artifact = deps.get("test")
|
|
89
|
-
if not test_artifact:
|
|
90
|
-
return GuardResult(
|
|
91
|
-
passed=False, feedback="No test artifact in dependencies"
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
q: multiprocessing.Queue = multiprocessing.Queue()
|
|
95
|
-
p = multiprocessing.Process(
|
|
96
|
-
target=self._run_tests, args=(artifact, test_artifact, q)
|
|
97
|
-
)
|
|
98
|
-
p.start()
|
|
99
|
-
p.join(self.timeout)
|
|
100
|
-
|
|
101
|
-
if p.is_alive():
|
|
102
|
-
p.terminate()
|
|
103
|
-
p.join()
|
|
104
|
-
return GuardResult(
|
|
105
|
-
passed=False,
|
|
106
|
-
feedback=f"Timeout: Test execution exceeded {self.timeout}s",
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
if not q.empty():
|
|
110
|
-
passed, msg = q.get()
|
|
111
|
-
return GuardResult(passed=passed, feedback=msg)
|
|
112
|
-
return GuardResult(passed=False, feedback="Test execution crashed")
|
|
113
|
-
|
|
114
|
-
def _run_tests(
|
|
115
|
-
self, impl_artifact: Artifact, test_artifact: Artifact, q: Any
|
|
116
|
-
) -> None:
|
|
117
|
-
"""
|
|
118
|
-
Execute tests in subprocess.
|
|
119
|
-
|
|
120
|
-
This method runs in a forked process for isolation.
|
|
121
|
-
"""
|
|
122
|
-
try:
|
|
123
|
-
impl_code = impl_artifact.content
|
|
124
|
-
test_code = test_artifact.content
|
|
125
|
-
|
|
126
|
-
if not impl_code:
|
|
127
|
-
q.put((False, "No implementation code"))
|
|
128
|
-
return
|
|
129
|
-
|
|
130
|
-
if not test_code:
|
|
131
|
-
q.put((False, "No test code"))
|
|
132
|
-
return
|
|
133
|
-
|
|
134
|
-
# Create mock 'implementation' module
|
|
135
|
-
impl_module = types.ModuleType("implementation")
|
|
136
|
-
exec(impl_code, impl_module.__dict__)
|
|
137
|
-
sys.modules["implementation"] = impl_module
|
|
138
|
-
|
|
139
|
-
# Execute test code (pytest already in sys.modules from parent)
|
|
140
|
-
import pytest
|
|
141
|
-
|
|
142
|
-
test_scope = {"__builtins__": __builtins__, "pytest": pytest}
|
|
143
|
-
exec(test_code, test_scope)
|
|
144
|
-
|
|
145
|
-
# Find and run test functions
|
|
146
|
-
test_funcs = [
|
|
147
|
-
v
|
|
148
|
-
for k, v in test_scope.items()
|
|
149
|
-
if k.startswith("test_") and callable(v)
|
|
150
|
-
]
|
|
151
|
-
|
|
152
|
-
if not test_funcs:
|
|
153
|
-
q.put((False, "No test functions found"))
|
|
154
|
-
return
|
|
155
|
-
|
|
156
|
-
failures = []
|
|
157
|
-
for func in test_funcs:
|
|
158
|
-
try:
|
|
159
|
-
func()
|
|
160
|
-
except AssertionError as e:
|
|
161
|
-
failures.append(f"{func.__name__}: AssertionError - {e}")
|
|
162
|
-
except Exception as e:
|
|
163
|
-
failures.append(f"{func.__name__}: {type(e).__name__} - {e}")
|
|
164
|
-
|
|
165
|
-
if failures:
|
|
166
|
-
q.put((False, "Test failures:\n" + "\n".join(failures)))
|
|
167
|
-
else:
|
|
168
|
-
q.put((True, f"All {len(test_funcs)} tests passed"))
|
|
169
|
-
|
|
170
|
-
except SyntaxError as e:
|
|
171
|
-
q.put((False, f"Syntax error: {e}"))
|
|
172
|
-
except Exception as e:
|
|
173
|
-
q.put((False, f"Execution error: {e}"))
|
|
174
|
-
finally:
|
|
175
|
-
if "implementation" in sys.modules:
|
|
176
|
-
del sys.modules["implementation"]
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
atomicguard/__init__.py,sha256=kiNxqjbVSlSf6Z0U7wpus3o_eMTB7M5-Dj41qgcgUwI,2927
|
|
2
|
-
atomicguard/application/__init__.py,sha256=-myJt7VuC1tMjO424NhjY6iZZuUdHGnFCJKFapFCAhw,409
|
|
3
|
-
atomicguard/application/action_pair.py,sha256=pa9gw4cTb8HaZLT_VqjQaKVMGGllo0BcDf6YzqsJlnA,1959
|
|
4
|
-
atomicguard/application/agent.py,sha256=uzwhrn43JkrK1xxuw8ETvjL4IAiVZ1_Jeik4tMY_2ZE,4254
|
|
5
|
-
atomicguard/application/workflow.py,sha256=CPnLVaten4hN4fiC_gGMvN9-BCJIVVwYsQb3HtRTlJg,4902
|
|
6
|
-
atomicguard/domain/__init__.py,sha256=z7CWqBs21XsZPLeuqLD1fNQVtl9VxIs3zkrK_1n4uD0,1061
|
|
7
|
-
atomicguard/domain/exceptions.py,sha256=3L1acckIcf0XewBy8QJpzgCRBaKyYdossbIAikt1Xkw,770
|
|
8
|
-
atomicguard/domain/interfaces.py,sha256=3kPXRof0a-4c225d1DgZ_66_5A6P8H1XZK-gTp9YkCI,2935
|
|
9
|
-
atomicguard/domain/models.py,sha256=p9FqB9MdriV7djaGUkREGEgkcWyhGvpYmHYrLq2VX-w,4483
|
|
10
|
-
atomicguard/domain/prompts.py,sha256=SMGcP0kYc9azCbGku5EG0JZZs83D80WkJO-DWiuFV58,2785
|
|
11
|
-
atomicguard/guards/__init__.py,sha256=pd4H3sfnrN76vKBX3KrXWsoe27V5vNacSEUKW9uIbmM,548
|
|
12
|
-
atomicguard/guards/base.py,sha256=VGydlOD4gmXdiDHvWXF5jSppXcJAoJYSCyDj7Lrbng0,1213
|
|
13
|
-
atomicguard/guards/human.py,sha256=G8SstAEdcMpHR2b8ugyG9drk9aBjw4_pE6E4UrTtcUo,2960
|
|
14
|
-
atomicguard/guards/syntax.py,sha256=mPVgGDY3pzwtXuulmmuEwYAQG7rNG0fgSGB1puYRI6Y,919
|
|
15
|
-
atomicguard/guards/test_runner.py,sha256=dIFtyOXxu_dxKDlrsBl_G7kCR5oy5gLQESQtYGslKO4,5538
|
|
16
|
-
atomicguard/infrastructure/__init__.py,sha256=98eYKEgEIzJ05Z5r82M7tB0S0QGD2PrfPR_8wapzfHA,465
|
|
17
|
-
atomicguard/infrastructure/llm/__init__.py,sha256=lmr-cYVeaiAsMi_DWcoM7G1wEs6Y6GS02RZeOs4GI-U,234
|
|
18
|
-
atomicguard/infrastructure/llm/mock.py,sha256=OVumDkWYe9xU8KQHdt79evpHzIJIRRxXIqeC9fgI-hQ,1840
|
|
19
|
-
atomicguard/infrastructure/llm/ollama.py,sha256=jtE3SXfMSsYUDocuKXDH3H4NrR8cMwjvCpSFkn99Xlg,4019
|
|
20
|
-
atomicguard/infrastructure/persistence/__init__.py,sha256=DdoD0V-uCetJDo_RJRPiCfjae3xAX5yofnQWbalBrJw,285
|
|
21
|
-
atomicguard/infrastructure/persistence/filesystem.py,sha256=LpdExc9FAwB2Od8eM5zSEMx4hC4Lx2AaztJdba8_MD0,8638
|
|
22
|
-
atomicguard/infrastructure/persistence/memory.py,sha256=gZo2hcpi5VOu60nscxPnyQBDg8P87MI9ZXBdaW0eebQ,1339
|
|
23
|
-
atomicguard-0.1.0.dist-info/licenses/LICENSE,sha256=ROMMFVruZ18U24pKTNte9AK_YIqoMfXnMzoxqBNmKS4,1073
|
|
24
|
-
atomicguard-0.1.0.dist-info/METADATA,sha256=yJlnBW1n_-4ORhWkIjTOykpD9vf6N9OHzO4mJIh39lc,4919
|
|
25
|
-
atomicguard-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
26
|
-
atomicguard-0.1.0.dist-info/top_level.txt,sha256=J_6ENELjnacSYJ5N3FGwomp-sVeAJQohPkJBh6pu6iY,12
|
|
27
|
-
atomicguard-0.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|