@oleksandr.rudnychenko/sync_loop 0.2.5 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +25 -4
  2. package/bin/cli.js +3 -128
  3. package/bin/cli.ts +171 -0
  4. package/dist/bin/cli.d.ts +15 -0
  5. package/dist/bin/cli.js +137 -0
  6. package/dist/bin/cli.js.map +1 -0
  7. package/dist/src/init.d.ts +24 -0
  8. package/dist/src/init.js +410 -0
  9. package/dist/src/init.js.map +1 -0
  10. package/dist/src/server.d.ts +13 -0
  11. package/dist/src/server.js +265 -0
  12. package/dist/src/server.js.map +1 -0
  13. package/dist/src/template/.agent-loop/README.md +75 -0
  14. package/dist/src/template/.agent-loop/feedback.md +395 -0
  15. package/dist/src/template/.agent-loop/glossary.md +113 -0
  16. package/dist/src/template/.agent-loop/patterns/api-standards.md +132 -0
  17. package/dist/src/template/.agent-loop/patterns/code-patterns.md +300 -0
  18. package/dist/src/template/.agent-loop/patterns/refactoring-workflow.md +114 -0
  19. package/dist/src/template/.agent-loop/patterns/testing-guide.md +258 -0
  20. package/dist/src/template/.agent-loop/patterns.md +256 -0
  21. package/dist/src/template/.agent-loop/reasoning-kernel.md +521 -0
  22. package/dist/src/template/.agent-loop/validate-env.md +332 -0
  23. package/dist/src/template/.agent-loop/validate-n.md +321 -0
  24. package/dist/src/template/AGENTS.md +157 -0
  25. package/dist/src/template/README.md +144 -0
  26. package/dist/src/template/bootstrap-prompt.md +37 -0
  27. package/dist/src/template/protocol-summary.md +54 -0
  28. package/dist/src/template/wiring/agents-claude.md +203 -0
  29. package/dist/src/template/wiring/agents-github.md +211 -0
  30. package/dist/src/template/wiring/api-standards.md +15 -0
  31. package/dist/src/template/wiring/code-patterns.md +15 -0
  32. package/dist/src/template/wiring/feedback.md +18 -0
  33. package/dist/src/template/wiring/glossary.md +11 -0
  34. package/dist/src/template/wiring/patterns.md +18 -0
  35. package/dist/src/template/wiring/reasoning-kernel.md +18 -0
  36. package/dist/src/template/wiring/refactoring-workflow.md +15 -0
  37. package/dist/src/template/wiring/testing-guide.md +15 -0
  38. package/dist/src/template/wiring/validate-env.md +17 -0
  39. package/dist/src/template/wiring/validate-n.md +17 -0
  40. package/package.json +48 -34
  41. package/src/template/wiring/agents-claude.md +203 -0
  42. package/src/template/wiring/agents-github.md +211 -0
  43. package/src/template/wiring/api-standards.md +15 -0
  44. package/src/template/wiring/code-patterns.md +15 -0
  45. package/src/template/wiring/feedback.md +18 -0
  46. package/src/template/wiring/glossary.md +11 -0
  47. package/src/template/wiring/patterns.md +18 -0
  48. package/src/template/wiring/reasoning-kernel.md +18 -0
  49. package/src/template/wiring/refactoring-workflow.md +15 -0
  50. package/src/template/wiring/testing-guide.md +15 -0
  51. package/src/template/wiring/validate-env.md +17 -0
  52. package/src/template/wiring/validate-n.md +17 -0
  53. package/src/init.js +0 -569
  54. package/src/server.js +0 -292
@@ -0,0 +1,300 @@
1
+ # Code Patterns (P1–P11)
2
+
3
+ Reusable implementation patterns for layered application code.
4
+ Referenced from [../patterns.md](../patterns.md).
5
+
6
+ ---
7
+
8
+ ## P1 · Port/Adapter
9
+
10
+ Abstracts infrastructure behind protocol interfaces. Decouples domain logic from external systems.
11
+
12
+ ```python
13
+ # Port (interface/protocol)
14
+ class StoragePort(Protocol):
15
+ def search(
16
+ self,
17
+ collection: str,
18
+ query: str,
19
+ *,
20
+ filters: dict | None = None,
21
+ limit: int = 10,
22
+ ) -> list[Record]: ...
23
+
24
+ # Adapter (concrete implementation)
25
+ class DatabaseAdapter:
26
+ def __init__(self, client: DBClient) -> None:
27
+ self._client = client
28
+
29
+ def search(
30
+ self,
31
+ collection: str,
32
+ query: str,
33
+ *,
34
+ filters: dict | None = None,
35
+ limit: int = 10,
36
+ ) -> list[Record]:
37
+ # Implementation against real infrastructure
38
+ ...
39
+ ```
40
+
41
+ **Key rules:**
42
+ - Port lives in `libs/{component}/port.*`
43
+ - Adapter lives in `libs/{component}/{impl}.*`
44
+ - Services depend on port interfaces, not adapters directly
45
+
46
+ ---
47
+
48
+ ## P2 · Domain Module
49
+
50
+ Each domain module follows a consistent multi-file layout:
51
+
52
+ | File | Purpose |
53
+ |------|---------|
54
+ | `models.*` | Domain entities and value objects |
55
+ | `services.*` | Business logic and orchestration |
56
+ | `routes.*` | Transport endpoints |
57
+ | `tasks.*` | Background tasks (if async processing needed) |
58
+
59
+ ```python
60
+ # services.py — business logic only, no transport concerns
61
+ class OrderService:
62
+ def __init__(self, repository: OrderRepository) -> None:
63
+ self._repository = repository
64
+
65
+ def process(self, *, order_id: str) -> ProcessResult:
66
+ order = self._repository.get(order_id)
67
+ # business logic here
68
+ return ProcessResult(order_id=order.id, status="completed")
69
+ ```
70
+
71
+ ---
72
+
73
+ ## P3 · Background Task Boundary
74
+
75
+ Task handlers stay thin. Business logic always lives in services.
76
+
77
+ ```python
78
+ # tasks.py — thin wrapper, delegates to service
79
+ def process_order_task(runtime: TaskRuntime, order_id: str):
80
+ """Background task that delegates to service."""
81
+ service = runtime.order_service
82
+ service.update_status(order_id, status="processing")
83
+
84
+ try:
85
+ service.process(order_id=order_id)
86
+ service.update_status(order_id, status="completed")
87
+ except Exception as exc:
88
+ service.update_status(order_id, status="failed", error=str(exc))
89
+ raise
90
+ ```
91
+
92
+ **Key rules:**
93
+ - Tasks never contain business logic
94
+ - Dependencies injected via runtime, not imported directly
95
+ - Always update status on success and failure
96
+
97
+ ---
98
+
99
+ ## P4 · App Context / Composition Root
100
+
101
+ Centralized dependency wiring, initialized once at startup:
102
+
103
+ ```python
104
+ @dataclass
105
+ class AppContext:
106
+ config: Config
107
+ session_factory: SessionFactory
108
+ services: ServiceRegistry
109
+ logger: Logger
110
+
111
+ _context: AppContext | None = None
112
+
113
+ def init_app_context(config: Config) -> AppContext:
114
+ global _context
115
+ if _context:
116
+ return _context
117
+ _context = AppContext(config=config, ...)
118
+ return _context
119
+
120
+ def get_app_context() -> AppContext:
121
+ if _context is None:
122
+ return init_app_context(load_config())
123
+ return _context
124
+ ```
125
+
126
+ ---
127
+
128
+ ## P5 · Transport Route
129
+
130
+ Routes only handle transport concerns; all logic delegated to services:
131
+
132
+ ```python
133
+ router = APIRouter()
134
+
135
+ def get_service() -> OrderService:
136
+ ctx = get_app_context()
137
+ return OrderService(ctx.repository)
138
+
139
+ @router.post("/orders")
140
+ def create_order(
141
+ data: CreateOrderRequest,
142
+ service: OrderService = Depends(get_service),
143
+ ) -> OrderResponse:
144
+ result = service.create(data)
145
+ return OrderResponse(id=result.id, status=result.status)
146
+ ```
147
+
148
+ ---
149
+
150
+ ## P6 · Typed Models
151
+
152
+ Domain entities with explicit types and serialization:
153
+
154
+ ```python
155
+ @dataclass(slots=True)
156
+ class OrderItem:
157
+ product_id: str
158
+ quantity: int
159
+ unit_price: float
160
+ tags: list[str] = field(default_factory=list)
161
+
162
+ def to_dict(self) -> dict[str, Any]:
163
+ return {
164
+ "product_id": self.product_id,
165
+ "quantity": self.quantity,
166
+ "unit_price": self.unit_price,
167
+ "tags": self.tags,
168
+ }
169
+ ```
170
+
171
+ ---
172
+
173
+ ## P7 · Collection/Enum Safety
174
+
175
+ Replace magic strings with typed enums:
176
+
177
+ ```python
178
+ class Collection(str, Enum):
179
+ ORDERS = "orders"
180
+ PRODUCTS = "products"
181
+ USERS = "users"
182
+
183
+ # Usage: repository.query(Collection.ORDERS, ...)
184
+ # NOT: repository.query("orders", ...)
185
+ ```
186
+
187
+ ---
188
+
189
+ ## P8 · Error Handling
190
+
191
+ Layered exception hierarchy with boundary translation:
192
+
193
+ ```python
194
+ # Domain exceptions
195
+ class DomainError(Exception):
196
+ """Base error for domain."""
197
+
198
+ class NotFoundError(DomainError):
199
+ """Resource not found."""
200
+
201
+ class ValidationError(DomainError):
202
+ """Invalid input or state."""
203
+
204
+ # Route-level translation
205
+ @router.get("/orders/{order_id}")
206
+ def get_order(order_id: str, service = Depends(get_service)):
207
+ try:
208
+ return service.get(order_id)
209
+ except NotFoundError as exc:
210
+ raise HTTPException(status_code=404, detail=str(exc))
211
+ except ValidationError as exc:
212
+ raise HTTPException(status_code=400, detail=str(exc))
213
+ ```
214
+
215
+ ---
216
+
217
+ ## P9 · Type Hints Everywhere
218
+
219
+ All code must have complete type annotations:
220
+
221
+ ```python
222
+ # ✅ Good — fully typed
223
+ def process(
224
+ order_id: str,
225
+ *,
226
+ callback: Callable[..., Awaitable[Response]] | None = None,
227
+ ) -> tuple[str, dict[str, Any]] | None:
228
+ ...
229
+
230
+ # ❌ Bad — missing annotations
231
+ def process(order_id, callback=None):
232
+ ...
233
+ ```
234
+
235
+ **Common type aliases:**
236
+ ```python
237
+ SessionFactory = Callable[[], Session]
238
+ Filters = Mapping[str, Any]
239
+ ```
240
+
241
+ ---
242
+
243
+ ## P10 · Service Orchestration
244
+
245
+ Services accept all dependencies via constructor — no hidden state:
246
+
247
+ ```python
248
+ # Production code
249
+ class AnalysisService:
250
+ def __init__(
251
+ self,
252
+ repository: Repository,
253
+ evaluator: EvaluationService,
254
+ ):
255
+ self._repository = repository
256
+ self._evaluator = evaluator
257
+
258
+ # Test code — inject mocks
259
+ service = AnalysisService(
260
+ repository=mock_repository,
261
+ evaluator=mock_evaluator,
262
+ )
263
+ ```
264
+
265
+ ---
266
+
267
+ ## P11 · Config Isolation
268
+
269
+ Centralized, environment-based configuration with startup validation:
270
+
271
+ ```python
272
+ @dataclass
273
+ class Config:
274
+ database_url: str
275
+ debug: bool = False
276
+ max_workers: int = 4
277
+
278
+ @classmethod
279
+ def from_env(cls) -> "Config":
280
+ return cls(
281
+ database_url=os.environ["DATABASE_URL"],
282
+ debug=os.environ.get("DEBUG", "0") == "1",
283
+ max_workers=int(os.environ.get("MAX_WORKERS", "4")),
284
+ )
285
+ ```
286
+
287
+ **Key rules:**
288
+ - All config read from environment at startup
289
+ - No scattered `os.environ` calls inside business logic
290
+ - Test config overrides controlled via fixtures
291
+
292
+ ---
293
+
294
+ ## Related Documents
295
+
296
+ | Document | Purpose |
297
+ |----------|---------|
298
+ | [../patterns.md](../patterns.md) | Pattern routing index |
299
+ | [refactoring-workflow.md](refactoring-workflow.md) | Safe structural changes |
300
+ | [testing-guide.md](testing-guide.md) | Verification strategy |
@@ -0,0 +1,114 @@
1
+ # Refactoring Workflow (R1)
2
+
3
+ Checklist and approach for moving files, changing imports, or restructuring modules.
4
+ Referenced from [../patterns.md](../patterns.md).
5
+
6
+ ---
7
+
8
+ ## Refactoring Checklist
9
+
10
+ ```
11
+ Phase 1: PLAN
12
+ ☐ Identify all files to move/rename/extract
13
+ ☐ Map old imports → new imports
14
+ ☐ Check for documentation references (README, docstrings, agent-loop specs)
15
+ ☐ Identify public interfaces that must remain stable
16
+
17
+ Phase 2: EXECUTE
18
+ ☐ Move files
19
+ ☐ Update imports in moved files (internal references)
20
+ ☐ Update all caller imports (use grep to find every reference)
21
+ ☐ Update documentation examples if they contain import paths
22
+ ☐ Update .agent-loop/patterns.md structure section if layout changed
23
+
24
+ Phase 3: VALIDATE (NON-NEGOTIABLE)
25
+ ☐ 1. Type check: run project type checker
26
+ ☐ 2. Run tests: execute test suite with fail-fast
27
+ ☐ 3. Check docs: grep for old import paths across all files
28
+ ☐ 4. Verify no orphaned imports (unused or broken)
29
+
30
+ Phase 4: DOCUMENT
31
+ ☐ Update README structure section
32
+ ☐ Update architecture docs if needed
33
+ ☐ Create report in docs/reports/ if the refactor is major
34
+ ```
35
+
36
+ ---
37
+
38
+ ## Example: Moving a Module File
39
+
40
+ ```bash
41
+ # 1. Move file
42
+ mv src/modules/old_location/processor.py src/modules/new_location/processor.py
43
+
44
+ # 2. Update imports inside the moved file (internal references)
45
+ # e.g., relative imports that changed due to new directory depth
46
+
47
+ # 3. Find ALL references to the old path
48
+ grep -r "from src.modules.old_location.processor" .
49
+ grep -r "import old_location.processor" .
50
+
51
+ # 4. Update each reference found:
52
+ # - tests/unit/test_processor.py
53
+ # - src/modules/new_location/tasks.py
54
+ # - docs/architecture.md (if it mentions import paths)
55
+
56
+ # 5. MANDATORY: Run validation
57
+ # Type check → Tests → Grep for leftover old paths
58
+ ```
59
+
60
+ **Why tests after docs?** Documentation often contains import examples. Tests verify imports actually work and catch typos in updated paths.
61
+
62
+ ---
63
+
64
+ ## Example: Extracting a Function to a New Module
65
+
66
+ ```bash
67
+ # 1. Create the new module
68
+ touch src/libs/parsing/helpers.py
69
+
70
+ # 2. Move the function definition to the new file
71
+ # Update its internal imports
72
+
73
+ # 3. In the original file, replace the function body with an import:
74
+ # from src.libs.parsing.helpers import parse_response
75
+
76
+ # 4. Find all OTHER callers of the original location
77
+ grep -r "from src.modules.analysis.utils import parse_response" .
78
+
79
+ # 5. Update all callers to use the new import path
80
+
81
+ # 6. VALIDATE: type check + tests + grep for old path
82
+ ```
83
+
84
+ ---
85
+
86
+ ## Guardrails
87
+
88
+ - **Prefer reversible steps** — move one file at a time, validate, then move next
89
+ - **Never combine unrelated refactors** — one logical change per commit
90
+ - **Do not hide breaking API changes** — if a public interface moved, update all consumers
91
+ - **Never skip Phase 3** — validation is non-negotiable, even for "simple" moves
92
+ - **Grep is your friend** — always search for the old path after moving; IDEs miss things
93
+
94
+ ---
95
+
96
+ ## Common Pitfalls
97
+
98
+ | Pitfall | Prevention |
99
+ |---------|------------|
100
+ | Circular imports after move | Map dependency graph before moving |
101
+ | Tests pass but type checker fails | Always run type checker first |
102
+ | Docs reference old paths | Grep docs/ and *.md files explicitly |
103
+ | Forgot to update __init__.py re-exports | Check all `__init__.py` files in affected packages |
104
+ | Moved too many files at once | Move one file → validate → repeat |
105
+
106
+ ---
107
+
108
+ ## Related Documents
109
+
110
+ | Document | Purpose |
111
+ |----------|---------|
112
+ | [../validate-env.md](../validate-env.md) | Stage 1 gates (type check, tests) |
113
+ | [../validate-n.md](../validate-n.md) | Stage 2 neighbor checks (boundary impact) |
114
+ | [testing-guide.md](testing-guide.md) | Regression test strategy |
@@ -0,0 +1,258 @@
1
+ # Testing Guide (R2)
2
+
3
+ Comprehensive testing patterns for safe iteration and regression prevention.
4
+ Referenced from [../patterns.md](../patterns.md).
5
+
6
+ ---
7
+
8
+ ## Test Organization
9
+
10
+ ```
11
+ tests/
12
+ ├── conftest.py # Shared fixtures, global setup
13
+ ├── factories.py # Test data builders
14
+ ├── mocks.py # Mock implementations for external deps
15
+ ├── api/ # API/endpoint tests (HTTP client)
16
+ ├── integration/ # Multi-component workflow tests
17
+ └── unit/ # Pure logic tests (no I/O)
18
+ ```
19
+
20
+ ---
21
+
22
+ ## Pattern 1: Fixture-Based Setup
23
+
24
+ ```python
25
+ # Environment isolation — reset global state each test
26
+ @pytest.fixture(autouse=True)
27
+ def reset_context():
28
+ """Reset global state before each test."""
29
+ import app.context
30
+ app.context._context = None
31
+ yield
32
+ app.context._context = None
33
+
34
+ # Test app configuration
35
+ @pytest.fixture
36
+ def test_app(monkeypatch: pytest.MonkeyPatch, tmp_path: Path):
37
+ """Configure app with test-safe environment."""
38
+ monkeypatch.setenv("APP_ENV", "testing")
39
+ monkeypatch.setenv("ENABLE_BACKGROUND_TASKS", "0")
40
+ yield create_app()
41
+
42
+ # API client for endpoint tests
43
+ @pytest.fixture
44
+ def client(test_app):
45
+ return TestClient(test_app)
46
+ ```
47
+
48
+ ---
49
+
50
+ ## Pattern 2: Factory Pattern for Test Data
51
+
52
+ ```python
53
+ class EntityFactory:
54
+ @staticmethod
55
+ def create(
56
+ id: str | None = None,
57
+ name: str = "Test Entity",
58
+ entity_type: str = "default",
59
+ **extras: Any,
60
+ ) -> dict[str, Any]:
61
+ return {
62
+ "id": id or str(uuid4()),
63
+ "name": name,
64
+ "metadata": {"type": entity_type, **extras},
65
+ }
66
+
67
+ @staticmethod
68
+ def with_full_analysis() -> dict[str, Any]:
69
+ """Semantic constructor for entity with all analysis fields populated."""
70
+ return EntityFactory.create(
71
+ name="Fully Analyzed Entity",
72
+ entity_type="analyzed",
73
+ score=85.0,
74
+ grade="B",
75
+ )
76
+ ```
77
+
78
+ ---
79
+
80
+ ## Pattern 3: Mock External Dependencies
81
+
82
+ ```python
83
+ class MockExternalService:
84
+ """Mock for any external service boundary (LLM, API, storage)."""
85
+ def __init__(self, response: dict | str | None = None):
86
+ self.response = response or self._default_response()
87
+ self.calls: list[dict] = []
88
+
89
+ async def __call__(self, prompt: str, context: str, **kwargs):
90
+ self.calls.append({"prompt": prompt, "context": context})
91
+ return json.dumps(self.response), {}
92
+
93
+ @property
94
+ def call_count(self) -> int:
95
+ return len(self.calls)
96
+ ```
97
+
98
+ Usage:
99
+ ```python
100
+ @pytest.fixture
101
+ def mock_service() -> MockExternalService:
102
+ return MockExternalService(response={"score": 85})
103
+
104
+ def test_analysis_delegates_to_service(mock_service):
105
+ service = AnalysisService(external=mock_service)
106
+ result = service.analyze(...)
107
+ assert mock_service.call_count == 1
108
+ ```
109
+
110
+ ---
111
+
112
+ ## Pattern 4: Class-Based Test Organization
113
+
114
+ ```python
115
+ class TestParseResponse:
116
+ def test_valid_json_response(self):
117
+ response = '{"entity_type": "analyzed", "score": 85}'
118
+ result = parse_response(response)
119
+ assert result.entity_type == EntityType.ANALYZED
120
+
121
+ def test_invalid_json_fallback(self):
122
+ result = parse_response("Not JSON")
123
+ assert result.entity_type == EntityType.UNKNOWN
124
+
125
+ def test_missing_fields_use_defaults(self):
126
+ result = parse_response('{"entity_type": "analyzed"}')
127
+ assert result.score == 0.0
128
+ ```
129
+
130
+ ---
131
+
132
+ ## Pattern 5: Parametrized Tests
133
+
134
+ ```python
135
+ @pytest.mark.parametrize("score,expected_grade", [
136
+ (95, "A"), (90, "A"),
137
+ (89.9, "B"), (85, "B"), (80, "B"),
138
+ (79, "C"), (70, "C"),
139
+ (69, "D"), (60, "D"),
140
+ (59, "F"), (0, "F"),
141
+ ])
142
+ def test_grade_thresholds(score: float, expected_grade: str):
143
+ assert compute_grade(score) == expected_grade
144
+ ```
145
+
146
+ ---
147
+
148
+ ## Pattern 6: Three-Layer Test Strategy
149
+
150
+ ```python
151
+ # UNIT: Pure logic, no I/O
152
+ def test_parse_response():
153
+ result = parse_response('{"entity_type": "analyzed"}')
154
+ assert result.entity_type == EntityType.ANALYZED
155
+
156
+ # INTEGRATION: Multiple components, mocked external I/O
157
+ def test_analysis_pipeline(mock_service, mock_repository):
158
+ service = AnalysisService(external=mock_service, repo=mock_repository)
159
+ report = service.analyze(entities=[...])
160
+ assert report.aggregate_score > 0
161
+
162
+ # API: HTTP endpoint, full stack with test client
163
+ def test_analyze_endpoint(client, mock_data):
164
+ response = client.post("/api/v1/analyze", json=mock_data)
165
+ assert response.status_code == 200
166
+ assert "score" in response.json()
167
+ ```
168
+
169
+ ---
170
+
171
+ ## Pattern 7: Assertion Patterns
172
+
173
+ ```python
174
+ # ✅ Specific property checks
175
+ assert report.task_id == "task-1"
176
+ assert len(report.metrics) > 0
177
+ assert "total_items" in [m.name for m in report.metrics]
178
+
179
+ # ✅ Range/constraint checks
180
+ assert 0 <= result.score <= 100
181
+ assert result.entity_type in EntityType
182
+
183
+ # ✅ Collection checks
184
+ assert all(m.value >= 0 for m in report.metrics)
185
+ assert any(r.entity_type == "analyzed" for r in results)
186
+
187
+ # ❌ Vague — what is being verified?
188
+ assert report
189
+ assert result is not None
190
+ ```
191
+
192
+ ---
193
+
194
+ ## Pattern 8: Naming Convention
195
+
196
+ Format: `test_<action>_<condition>_<outcome>`
197
+
198
+ ```
199
+ test_returns_report # Basic happy path
200
+ test_metrics_computed_correctly # Specific behavior
201
+ test_unknown_type_falls_back_to_default # Edge case
202
+ test_invalid_json_fallback # Error handling
203
+ test_missing_fields_use_defaults # Default behavior
204
+ test_empty_input_returns_empty_report # Boundary condition
205
+ ```
206
+
207
+ ---
208
+
209
+ ## Test Metrics (Target)
210
+
211
+ | Metric | Target |
212
+ |--------|--------|
213
+ | Pass Rate | 100% |
214
+ | Unit proportion | ≥ 70% |
215
+ | Integration proportion | ≤ 20% |
216
+ | API proportion | ≤ 10% |
217
+
218
+ ---
219
+
220
+ ## Test Heuristics
221
+
222
+ | Heuristic | Guideline |
223
+ |-----------|-----------|
224
+ | **Speed** | Unit < 10ms, Integration < 100ms, API < 1s |
225
+ | **Isolation** | Each test independent, no shared mutable state |
226
+ | **Coverage** | Critical paths and edge cases tested, not chasing 100% line coverage |
227
+ | **Clarity** | Test name explains what behavior is being verified |
228
+ | **Simplicity** | One logical assertion per test (multiple `assert` OK if testing one concept) |
229
+ | **Structure** | Arrange → Act → Assert (AAA pattern) |
230
+ | **Determinism** | No flaky tests — mock time, randomness, external systems |
231
+
232
+ ---
233
+
234
+ ## Validation Strategy
235
+
236
+ 1. **Changed symbols first** — run tests for files you changed
237
+ 2. **Adjacent modules second** — run tests for callers/consumers
238
+ 3. **Full suite last** — once local confidence is high, run everything
239
+
240
+ ```bash
241
+ # Targeted
242
+ pytest tests/unit/test_processor.py -x -v
243
+
244
+ # Adjacent
245
+ pytest tests/unit/ tests/integration/ -x -v -k "processor or analysis"
246
+
247
+ # Full suite
248
+ pytest tests/ -x -v
249
+ ```
250
+
251
+ ---
252
+
253
+ ## Related Documents
254
+
255
+ | Document | Purpose |
256
+ |----------|---------|
257
+ | [../validate-env.md](../validate-env.md) | Gate orchestration (test gate details) |
258
+ | [refactoring-workflow.md](refactoring-workflow.md) | Safe code movement (tests after each move) |