atomicguard 0.1.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.
@@ -0,0 +1,132 @@
1
+ """
2
+ Ollama LLM generator implementation.
3
+
4
+ Connects to Ollama instances via the OpenAI-compatible API.
5
+ """
6
+
7
+ import re
8
+ import uuid
9
+ from datetime import datetime
10
+ from typing import Any, cast
11
+
12
+ from atomicguard.domain.interfaces import GeneratorInterface
13
+ from atomicguard.domain.models import (
14
+ Artifact,
15
+ ArtifactStatus,
16
+ Context,
17
+ ContextSnapshot,
18
+ )
19
+ from atomicguard.domain.prompts import PromptTemplate
20
+
21
+ DEFAULT_OLLAMA_URL = "http://localhost:11434/v1"
22
+
23
+
24
+ class OllamaGenerator(GeneratorInterface):
25
+ """Connects to Ollama instance using OpenAI-compatible API."""
26
+
27
+ def __init__(
28
+ self,
29
+ model: str = "qwen2.5-coder:7b",
30
+ base_url: str = DEFAULT_OLLAMA_URL,
31
+ timeout: float = 120.0,
32
+ ):
33
+ """
34
+ Args:
35
+ model: Ollama model name
36
+ base_url: Ollama API URL
37
+ timeout: Request timeout in seconds
38
+ """
39
+ try:
40
+ from openai import OpenAI
41
+ except ImportError as err:
42
+ raise ImportError("openai library required: pip install openai") from err
43
+
44
+ self._model = model
45
+ self._client = OpenAI(
46
+ base_url=base_url,
47
+ api_key="ollama", # required but unused
48
+ timeout=timeout,
49
+ )
50
+ self._version_counter = 0
51
+
52
+ def generate(
53
+ self, context: Context, template: PromptTemplate | None = None
54
+ ) -> Artifact:
55
+ """Generate an artifact based on context."""
56
+ # Build prompt
57
+ if template:
58
+ prompt = template.render(context)
59
+ else:
60
+ prompt = self._build_basic_prompt(context)
61
+
62
+ # Call Ollama
63
+ messages = [
64
+ {
65
+ "role": "system",
66
+ "content": "You are a Python programming assistant. Provide complete, runnable code in a markdown block:\n```python\n# code\n```",
67
+ },
68
+ {"role": "user", "content": prompt},
69
+ ]
70
+
71
+ response = self._client.chat.completions.create(
72
+ model=self._model, messages=cast(Any, messages), temperature=0.7
73
+ )
74
+
75
+ content = response.choices[0].message.content or ""
76
+ code = self._extract_code(content)
77
+
78
+ self._version_counter += 1
79
+
80
+ return Artifact(
81
+ artifact_id=str(uuid.uuid4()),
82
+ content=code,
83
+ previous_attempt_id=None,
84
+ action_pair_id="ollama",
85
+ created_at=datetime.now().isoformat(),
86
+ attempt_number=self._version_counter,
87
+ status=ArtifactStatus.PENDING,
88
+ guard_result=None,
89
+ feedback="",
90
+ context=ContextSnapshot(
91
+ specification=context.specification,
92
+ constraints=context.ambient.constraints,
93
+ feedback_history=(),
94
+ dependency_ids=(),
95
+ ),
96
+ )
97
+
98
+ def _extract_code(self, content: str) -> str:
99
+ """Extract Python code from response."""
100
+ # Try python block
101
+ match = re.search(r"```python\n(.*?)\n```", content, re.DOTALL)
102
+ if match:
103
+ return match.group(1)
104
+
105
+ # Try generic block
106
+ match = re.search(r"```\n(.*?)\n```", content, re.DOTALL)
107
+ if match:
108
+ return match.group(1)
109
+
110
+ # Try first def/import/class
111
+ match = re.search(r"^(def |import |class )", content, re.MULTILINE)
112
+ if match:
113
+ return content[match.start() :]
114
+
115
+ # Fallback: full content
116
+ return content
117
+
118
+ def _build_basic_prompt(self, context: Context) -> str:
119
+ """Build a basic prompt from context."""
120
+ parts = [context.specification]
121
+
122
+ if context.current_artifact:
123
+ parts.append(f"\nPrevious attempt:\n{context.current_artifact}")
124
+
125
+ if context.feedback_history:
126
+ feedback_text = "\n".join(
127
+ f"Attempt {i + 1} feedback: {f}"
128
+ for i, (_, f) in enumerate(context.feedback_history)
129
+ )
130
+ parts.append(f"\nFeedback history:\n{feedback_text}")
131
+
132
+ return "\n".join(parts)
@@ -0,0 +1,11 @@
1
+ """
2
+ Persistence adapters for the Artifact DAG.
3
+ """
4
+
5
+ from atomicguard.infrastructure.persistence.filesystem import FilesystemArtifactDAG
6
+ from atomicguard.infrastructure.persistence.memory import InMemoryArtifactDAG
7
+
8
+ __all__ = [
9
+ "InMemoryArtifactDAG",
10
+ "FilesystemArtifactDAG",
11
+ ]
@@ -0,0 +1,232 @@
1
+ """
2
+ Filesystem implementation of the Artifact DAG.
3
+
4
+ Provides persistent, append-only storage for artifacts.
5
+ Implements the Versioned Repository R from Definition 4.
6
+ """
7
+
8
+ import json
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ from atomicguard.domain.interfaces import ArtifactDAGInterface
13
+ from atomicguard.domain.models import (
14
+ Artifact,
15
+ ArtifactStatus,
16
+ ContextSnapshot,
17
+ FeedbackEntry,
18
+ )
19
+
20
+
21
+ class FilesystemArtifactDAG(ArtifactDAGInterface):
22
+ """
23
+ Persistent, append-only artifact repository.
24
+
25
+ Stores artifacts as JSON files with an index for efficient lookups.
26
+ Implements the Versioned Repository R from Definition 4.
27
+ """
28
+
29
+ def __init__(self, base_dir: str):
30
+ self._base_dir = Path(base_dir)
31
+ self._objects_dir = self._base_dir / "objects"
32
+ self._index_path = self._base_dir / "index.json"
33
+ self._cache: dict[str, Artifact] = {}
34
+ self._index: dict[str, Any] = self._load_or_create_index()
35
+
36
+ def _load_or_create_index(self) -> dict[str, Any]:
37
+ """Load existing index or create new one."""
38
+ self._base_dir.mkdir(parents=True, exist_ok=True)
39
+ self._objects_dir.mkdir(parents=True, exist_ok=True)
40
+
41
+ if self._index_path.exists():
42
+ with open(self._index_path) as f:
43
+ result: dict[str, Any] = json.load(f)
44
+ return result
45
+
46
+ return {"version": "1.0", "artifacts": {}, "action_pairs": {}}
47
+
48
+ def _update_index_atomic(self) -> None:
49
+ """Atomically update index.json using write-to-temp + rename."""
50
+ temp_path = self._index_path.with_suffix(".tmp")
51
+ with open(temp_path, "w") as f:
52
+ json.dump(self._index, f, indent=2)
53
+ temp_path.rename(self._index_path) # Atomic on POSIX
54
+
55
+ def _artifact_to_dict(self, artifact: Artifact) -> dict:
56
+ """Serialize artifact to JSON-compatible dict."""
57
+ return {
58
+ "artifact_id": artifact.artifact_id,
59
+ "content": artifact.content,
60
+ "previous_attempt_id": artifact.previous_attempt_id,
61
+ "action_pair_id": artifact.action_pair_id,
62
+ "created_at": artifact.created_at,
63
+ "attempt_number": artifact.attempt_number,
64
+ "status": artifact.status.value,
65
+ "guard_result": artifact.guard_result,
66
+ "feedback": artifact.feedback,
67
+ "context": {
68
+ "specification": artifact.context.specification,
69
+ "constraints": artifact.context.constraints,
70
+ "feedback_history": [
71
+ {"artifact_id": fe.artifact_id, "feedback": fe.feedback}
72
+ for fe in artifact.context.feedback_history
73
+ ],
74
+ "dependency_ids": list(artifact.context.dependency_ids),
75
+ },
76
+ }
77
+
78
+ def _dict_to_artifact(self, data: dict) -> Artifact:
79
+ """Deserialize artifact from JSON dict."""
80
+ context = ContextSnapshot(
81
+ specification=data["context"]["specification"],
82
+ constraints=data["context"]["constraints"],
83
+ feedback_history=tuple(
84
+ FeedbackEntry(artifact_id=fe["artifact_id"], feedback=fe["feedback"])
85
+ for fe in data["context"]["feedback_history"]
86
+ ),
87
+ dependency_ids=tuple(data["context"]["dependency_ids"]),
88
+ )
89
+ return Artifact(
90
+ artifact_id=data["artifact_id"],
91
+ content=data["content"],
92
+ previous_attempt_id=data["previous_attempt_id"],
93
+ action_pair_id=data["action_pair_id"],
94
+ created_at=data["created_at"],
95
+ attempt_number=data["attempt_number"],
96
+ status=ArtifactStatus(data["status"]),
97
+ guard_result=data["guard_result"],
98
+ feedback=data["feedback"],
99
+ context=context,
100
+ )
101
+
102
+ def _get_object_path(self, artifact_id: str) -> Path:
103
+ """Get filesystem path for artifact (using prefix directories)."""
104
+ prefix = artifact_id[:2]
105
+ return self._objects_dir / prefix / f"{artifact_id}.json"
106
+
107
+ def store(self, artifact: Artifact, _metadata: str = "") -> str:
108
+ """
109
+ Append artifact to DAG (immutable, append-only).
110
+
111
+ Args:
112
+ artifact: The artifact to store
113
+ metadata: Optional metadata string (for compatibility, stored in feedback)
114
+
115
+ Returns:
116
+ The artifact_id
117
+ """
118
+ # 1. Serialize to JSON
119
+ artifact_dict = self._artifact_to_dict(artifact)
120
+
121
+ # 2. Write to objects/{prefix}/{artifact_id}.json
122
+ object_path = self._get_object_path(artifact.artifact_id)
123
+ object_path.parent.mkdir(parents=True, exist_ok=True)
124
+ with open(object_path, "w") as f:
125
+ json.dump(artifact_dict, f, indent=2)
126
+
127
+ # 3. Update index
128
+ self._index["artifacts"][artifact.artifact_id] = {
129
+ "path": str(object_path.relative_to(self._base_dir)),
130
+ "action_pair_id": artifact.action_pair_id,
131
+ "status": artifact.status.value,
132
+ "created_at": artifact.created_at,
133
+ }
134
+
135
+ # Track by action pair
136
+ if artifact.action_pair_id not in self._index["action_pairs"]:
137
+ self._index["action_pairs"][artifact.action_pair_id] = []
138
+ self._index["action_pairs"][artifact.action_pair_id].append(
139
+ artifact.artifact_id
140
+ )
141
+
142
+ # 4. Atomically update index
143
+ self._update_index_atomic()
144
+
145
+ # 5. Add to cache
146
+ self._cache[artifact.artifact_id] = artifact
147
+
148
+ return artifact.artifact_id
149
+
150
+ def get_artifact(self, artifact_id: str) -> Artifact:
151
+ """Retrieve artifact by ID (cache-first)."""
152
+ # Check cache first
153
+ if artifact_id in self._cache:
154
+ return self._cache[artifact_id]
155
+
156
+ # Check index
157
+ if artifact_id not in self._index["artifacts"]:
158
+ raise KeyError(f"Artifact not found: {artifact_id}")
159
+
160
+ # Load from filesystem
161
+ rel_path = self._index["artifacts"][artifact_id]["path"]
162
+ object_path = self._base_dir / rel_path
163
+
164
+ with open(object_path) as f:
165
+ data = json.load(f)
166
+
167
+ artifact = self._dict_to_artifact(data)
168
+ self._cache[artifact_id] = artifact
169
+ return artifact
170
+
171
+ def get_provenance(self, artifact_id: str) -> list[Artifact]:
172
+ """Trace retry chain via previous_attempt_id."""
173
+ result = []
174
+ current_id: str | None = artifact_id
175
+
176
+ while current_id:
177
+ artifact = self.get_artifact(current_id)
178
+ result.append(artifact)
179
+ current_id = artifact.previous_attempt_id
180
+
181
+ return list(reversed(result))
182
+
183
+ def get_by_action_pair(self, action_pair_id: str) -> list[Artifact]:
184
+ """Get all artifacts for an action pair."""
185
+ if action_pair_id not in self._index["action_pairs"]:
186
+ return []
187
+
188
+ artifact_ids = self._index["action_pairs"][action_pair_id]
189
+ return [self.get_artifact(aid) for aid in artifact_ids]
190
+
191
+ def get_accepted(self, action_pair_id: str) -> Artifact | None:
192
+ """Get the accepted artifact for an action pair (if any)."""
193
+ artifacts = self.get_by_action_pair(action_pair_id)
194
+ for artifact in artifacts:
195
+ if artifact.status == ArtifactStatus.ACCEPTED:
196
+ return artifact
197
+ return None
198
+
199
+ def update_status(self, artifact_id: str, new_status: ArtifactStatus) -> None:
200
+ """
201
+ Update artifact status (e.g., mark as ACCEPTED or SUPERSEDED).
202
+
203
+ Note: This creates a new file version, preserving append-only semantics.
204
+ """
205
+ artifact = self.get_artifact(artifact_id)
206
+
207
+ # Create updated artifact (immutable, so we create new instance)
208
+ updated = Artifact(
209
+ artifact_id=artifact.artifact_id,
210
+ content=artifact.content,
211
+ previous_attempt_id=artifact.previous_attempt_id,
212
+ action_pair_id=artifact.action_pair_id,
213
+ created_at=artifact.created_at,
214
+ attempt_number=artifact.attempt_number,
215
+ status=new_status,
216
+ guard_result=artifact.guard_result,
217
+ feedback=artifact.feedback,
218
+ context=artifact.context,
219
+ )
220
+
221
+ # Update file
222
+ artifact_dict = self._artifact_to_dict(updated)
223
+ object_path = self._get_object_path(artifact_id)
224
+ with open(object_path, "w") as f:
225
+ json.dump(artifact_dict, f, indent=2)
226
+
227
+ # Update index
228
+ self._index["artifacts"][artifact_id]["status"] = new_status.value
229
+ self._update_index_atomic()
230
+
231
+ # Update cache
232
+ self._cache[artifact_id] = updated
@@ -0,0 +1,39 @@
1
+ """
2
+ In-memory implementation of the Artifact DAG.
3
+
4
+ Useful for testing and ephemeral workflows.
5
+ """
6
+
7
+ from atomicguard.domain.interfaces import ArtifactDAGInterface
8
+ from atomicguard.domain.models import Artifact
9
+
10
+
11
+ class InMemoryArtifactDAG(ArtifactDAGInterface):
12
+ """Simple in-memory DAG for testing."""
13
+
14
+ def __init__(self) -> None:
15
+ self._artifacts: dict[str, Artifact] = {}
16
+ self._metadata: dict[str, str] = {}
17
+
18
+ def store(self, artifact: Artifact, metadata: str = "") -> str:
19
+ self._artifacts[artifact.artifact_id] = artifact
20
+ self._metadata[artifact.artifact_id] = metadata
21
+ return artifact.artifact_id
22
+
23
+ def get_artifact(self, artifact_id: str) -> Artifact:
24
+ if artifact_id not in self._artifacts:
25
+ raise KeyError(f"Artifact not found: {artifact_id}")
26
+ return self._artifacts[artifact_id]
27
+
28
+ def get_provenance(self, artifact_id: str) -> list[Artifact]:
29
+ result = []
30
+ current = self._artifacts.get(artifact_id)
31
+ while current:
32
+ result.append(current)
33
+ if (
34
+ not hasattr(current, "previous_attempt_id")
35
+ or current.previous_attempt_id is None
36
+ ):
37
+ break
38
+ current = self._artifacts.get(current.previous_attempt_id)
39
+ return list(reversed(result))
@@ -0,0 +1,137 @@
1
+ Metadata-Version: 2.4
2
+ Name: atomicguard
3
+ Version: 0.1.0
4
+ Summary: A Dual-State Agent Framework for reliable LLM code generation with guard-validated loops
5
+ Author-email: Matthew Thompson <thompsonson@gmail.com>
6
+ Maintainer-email: Matthew Thompson <thompsonson@gmail.com>
7
+ License: MIT
8
+ Project-URL: Homepage, https://github.com/thompsonson/atomicguard
9
+ Project-URL: Repository, https://github.com/thompsonson/atomicguard
10
+ Project-URL: Documentation, https://github.com/thompsonson/atomicguard#readme
11
+ Project-URL: Issues, https://github.com/thompsonson/atomicguard/issues
12
+ Project-URL: Changelog, https://github.com/thompsonson/atomicguard/blob/main/CHANGELOG.md
13
+ Keywords: llm,agents,code-generation,neuro-symbolic,guards,ai,validation
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: Science/Research
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
24
+ Classifier: Topic :: Software Development :: Code Generators
25
+ Classifier: Typing :: Typed
26
+ Requires-Python: >=3.12
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Requires-Dist: click>=8.3.1
30
+ Requires-Dist: matplotlib>=3.10.0
31
+ Requires-Dist: openai>=2.12.0
32
+ Requires-Dist: rich>=14.0.0
33
+ Provides-Extra: dev
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"
40
+ Dynamic: license-file
41
+
42
+ # AtomicGuard
43
+
44
+ [![CI](https://github.com/thompsonson/atomicguard/actions/workflows/ci.yml/badge.svg)](https://github.com/thompsonson/atomicguard/actions/workflows/ci.yml)
45
+ [![codecov](https://codecov.io/gh/thompsonson/atomicguard/branch/main/graph/badge.svg)](https://codecov.io/gh/thompsonson/atomicguard)
46
+ [![PyPI version](https://badge.fury.io/py/atomicguard.svg)](https://badge.fury.io/py/atomicguard)
47
+ [![Python versions](https://img.shields.io/pypi/pyversions/atomicguard.svg)](https://pypi.org/project/atomicguard/)
48
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
49
+
50
+ A Dual-State Agent Framework for reliable LLM code generation.
51
+
52
+ > **New to AtomicGuard?** Start with the [Getting Started Guide](docs/getting-started.md).
53
+
54
+ **Paper:** *Managing the Stochastic: Foundations of Learning in Neuro-Symbolic Systems for Software Engineering* (Thompson, 2025)
55
+
56
+ ## Overview
57
+
58
+ AtomicGuard implements guard-validated generation loops that dramatically improve LLM reliability. The core abstraction is the **Atomic Action Pair** ⟨agen, G⟩ — coupling each generation action with a validation guard.
59
+
60
+ Key results (Yi-Coder 9B, n=50):
61
+
62
+ | Task | Baseline | Guarded | Improvement |
63
+ |------|----------|---------|-------------|
64
+ | Template | 35% | 90% | +55pp |
65
+ | Password | 82% | 98% | +16pp |
66
+ | LRU Cache | 94% | 100% | +6pp |
67
+
68
+ ## Installation
69
+
70
+ ```bash
71
+ # From PyPI
72
+ pip install atomicguard
73
+
74
+ # From source
75
+ git clone https://github.com/thompsonson/atomicguard.git
76
+ cd atomicguard
77
+ uv venv && source .venv/bin/activate
78
+ uv pip install -e ".[dev,test]"
79
+ ```
80
+
81
+ ## Quick Start
82
+
83
+ ```python
84
+ from atomicguard import (
85
+ OllamaGenerator, SyntaxGuard, TestGuard,
86
+ CompositeGuard, ActionPair, DualStateAgent,
87
+ InMemoryArtifactDAG
88
+ )
89
+
90
+ # Setup
91
+ generator = OllamaGenerator(model="qwen2.5-coder:7b")
92
+ guard = CompositeGuard([SyntaxGuard(), TestGuard("assert add(2, 3) == 5")])
93
+ action_pair = ActionPair(generator=generator, guard=guard)
94
+ agent = DualStateAgent(action_pair, InMemoryArtifactDAG(), rmax=3)
95
+
96
+ # Execute
97
+ artifact = agent.execute("Write a function that adds two numbers")
98
+ print(artifact.content)
99
+ ```
100
+
101
+ See [examples/](examples/) for more detailed usage, including a [mock example](examples/basic_mock.py) that works without an LLM.
102
+
103
+ ## Benchmarks
104
+
105
+ Run the simulation from the paper:
106
+
107
+ ```bash
108
+ python -m benchmarks.simulation --model yi-coder:9b --trials 50 --task all --output results/results.db --format sqlite
109
+
110
+ # Generate report
111
+ python -m benchmarks.simulation --visualize --output results/results.db --format sqlite
112
+ ```
113
+
114
+ ## Project Structure
115
+
116
+ ```
117
+ atomicguard/
118
+ ├── src/atomicguard/ # Core library
119
+ ├── benchmarks/ # Simulation code
120
+ ├── docs/design/ # Design documents
121
+ ├── examples/ # Usage examples
122
+ └── results/ # Generated reports & charts
123
+ ```
124
+
125
+ ## Citation
126
+
127
+ ```bibtex
128
+ @article{thompson2025managing,
129
+ title={Managing the Stochastic: Foundations of Learning in Neuro-Symbolic Systems for Software Engineering},
130
+ author={Thompson, Matthew},
131
+ year={2025}
132
+ }
133
+ ```
134
+
135
+ ## License
136
+
137
+ MIT
@@ -0,0 +1,27 @@
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,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Matthew Thompson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ atomicguard