devagent-ai 0.8.1__tar.gz → 0.8.2__tar.gz

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 (86) hide show
  1. {devagent_ai-0.8.1/devagent_ai.egg-info → devagent_ai-0.8.2}/PKG-INFO +1 -1
  2. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/devagent/__init__.py +1 -1
  3. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/devagent/tasking.py +128 -4
  4. {devagent_ai-0.8.1 → devagent_ai-0.8.2/devagent_ai.egg-info}/PKG-INFO +1 -1
  5. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/devagent_ai.egg-info/SOURCES.txt +1 -0
  6. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/pyproject.toml +1 -1
  7. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_production_v040.py +1 -1
  8. devagent_ai-0.8.2/tests/test_requirement_compiler_v082.py +185 -0
  9. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/LICENSE +0 -0
  10. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/NOTICE +0 -0
  11. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/README.md +0 -0
  12. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/agent/__init__.py +0 -0
  13. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/agent/llm.py +0 -0
  14. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/agent/loop.py +0 -0
  15. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/agent/memory.py +0 -0
  16. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/agent/prompts.py +0 -0
  17. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/agent/tools.py +0 -0
  18. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/devagent/__main__.py +0 -0
  19. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/devagent/artifacts.py +0 -0
  20. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/devagent/automations.py +0 -0
  21. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/devagent/autonomy.py +0 -0
  22. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/devagent/browser.py +0 -0
  23. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/devagent/cli.py +0 -0
  24. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/devagent/config.py +0 -0
  25. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/devagent/discovery.py +0 -0
  26. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/devagent/evaluation.py +0 -0
  27. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/devagent/memory.py +0 -0
  28. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/devagent/models.py +0 -0
  29. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/devagent/orchestrator.py +0 -0
  30. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/devagent/provider_benchmark.py +0 -0
  31. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/devagent/providers.py +0 -0
  32. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/devagent/qualification.py +0 -0
  33. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/devagent/realworld.py +0 -0
  34. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/devagent/report.py +0 -0
  35. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/devagent/retrieval.py +0 -0
  36. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/devagent/routing.py +0 -0
  37. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/devagent/runtime.py +0 -0
  38. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/devagent/safety.py +0 -0
  39. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/devagent/skills.py +0 -0
  40. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/devagent/source_control.py +0 -0
  41. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/devagent/state_machine.py +0 -0
  42. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/devagent/technical_review.py +0 -0
  43. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/devagent/workspace.py +0 -0
  44. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/devagent/worktree.py +0 -0
  45. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/devagent_ai.egg-info/dependency_links.txt +0 -0
  46. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/devagent_ai.egg-info/entry_points.txt +0 -0
  47. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/devagent_ai.egg-info/requires.txt +0 -0
  48. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/devagent_ai.egg-info/top_level.txt +0 -0
  49. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/setup.cfg +0 -0
  50. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_acceptance_contract.py +0 -0
  51. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_benchmark_catalog.py +0 -0
  52. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_browser_verification.py +0 -0
  53. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_capability_discovery.py +0 -0
  54. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_cli.py +0 -0
  55. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_developer_review_report.py +0 -0
  56. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_discovery_memory.py +0 -0
  57. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_e2e_fake_provider.py +0 -0
  58. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_evaluation_harness.py +0 -0
  59. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_evaluation_matrix.py +0 -0
  60. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_evaluation_regression_evidence.py +0 -0
  61. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_functional_qualification.py +0 -0
  62. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_huge_monorepo_v070.py +0 -0
  63. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_migration_e2e_v070.py +0 -0
  64. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_model_routing.py +0 -0
  65. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_multilang_technical_review.py +0 -0
  66. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_multistack_devagent_e2e.py +0 -0
  67. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_multistack_qualification.py +0 -0
  68. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_packaging_metadata.py +0 -0
  69. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_plan_verification_normalization.py +0 -0
  70. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_preservation_contradiction.py +0 -0
  71. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_production_hardening.py +0 -0
  72. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_realworld_benchmark.py +0 -0
  73. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_retrieval.py +0 -0
  74. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_runtime_sandbox.py +0 -0
  75. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_safety_workspace.py +0 -0
  76. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_source_control_publish.py +0 -0
  77. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_structural_devagent_e2e_v070.py +0 -0
  78. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_structural_operations.py +0 -0
  79. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_structured_provider_contract.py +0 -0
  80. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_tasking_state.py +0 -0
  81. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_v070_engineering_breadth.py +0 -0
  82. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_v080_autonomy.py +0 -0
  83. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_v080_provider_benchmark.py +0 -0
  84. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_v080_skills_automations.py +0 -0
  85. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_workspace_environment.py +0 -0
  86. {devagent_ai-0.8.1 → devagent_ai-0.8.2}/tests/test_worktree.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devagent-ai
3
- Version: 0.8.1
3
+ Version: 0.8.2
4
4
  Summary: Evidence-driven local autonomous software engineering agent
5
5
  Author: Tom Ha
6
6
  Maintainer: Tom Ha
@@ -1,3 +1,3 @@
1
1
  """DevAgent: evidence-driven local software engineering automation."""
2
2
 
3
- __version__ = "0.8.1"
3
+ __version__ = "0.8.2"
@@ -63,6 +63,18 @@ _DIRECTIVE = re.compile(
63
63
  re.IGNORECASE,
64
64
  )
65
65
 
66
+ # Bounded normalization for terse user intent. This is deliberately not a fuzzy
67
+ # "guess what the user meant" layer: it corrects common engineering shorthand,
68
+ # grammatical number, and operation wording while preserving identifiers,
69
+ # quoted contracts, values, and explicit constraints. Task policy and repository
70
+ # evidence still provide the verification/safety contract.
71
+ _OPERATION_ALIASES: tuple[tuple[str, str], ...] = (
72
+ ("substraction", "subtraction"),
73
+ ("substract", "subtract"),
74
+ ("multipy", "multiply"),
75
+ ("mutiply", "multiply"),
76
+ )
77
+
66
78
 
67
79
  def _classify(text: str) -> TaskType:
68
80
  lowered = text.lower()
@@ -98,6 +110,60 @@ def _dedupe(items: list[str]) -> list[str]:
98
110
  return result
99
111
 
100
112
 
113
+ def _normalize_terse_requirement(requirement: str) -> str:
114
+ """Compile common rough one-line prompts into a clearer engineering request.
115
+
116
+ The compiler is intentionally bounded. It may repair shorthand/grammar and
117
+ make an operation explicit, but it must not add product behavior the user did
118
+ not request. Structured/multi-line requirements are left intact.
119
+ """
120
+
121
+ value = re.sub(r"\s+", " ", requirement).strip()
122
+ if not value or "\n" in requirement or _section_header(value) is not None:
123
+ return value
124
+ # An explicit callable name is already a precise user contract; never rename it.
125
+ if re.search(r"\b[A-Za-z_][A-Za-z0-9_]*\s*\(", value):
126
+ return value
127
+
128
+ for source, destination in _OPERATION_ALIASES:
129
+ value = re.sub(rf"\b{re.escape(source)}\b", destination, value, flags=re.IGNORECASE)
130
+
131
+ # Common shorthand from natural prompts such as "addition 2 matrix 2x2".
132
+ # Keep both "matrix" and "matrices" in the normalized contract so
133
+ # deterministic evidence can link either conventional symbol spelling.
134
+ matrix_match = re.search(
135
+ r"\b(add(?:ition)?|sum|subtract(?:ion)?|multiply|multiplication|divide|division)\b"
136
+ r"(?:\s+(?:of|for))?\s+(?:2|two)\s+matrix(?:es)?\s+(\d+x\d+)\b",
137
+ value,
138
+ flags=re.IGNORECASE,
139
+ )
140
+ if matrix_match:
141
+ operation = matrix_match.group(1).lower()
142
+ dimension = matrix_match.group(2).lower()
143
+ canonical_operation = {
144
+ "add": "addition",
145
+ "addition": "addition",
146
+ "sum": "addition",
147
+ "subtract": "subtraction",
148
+ "subtraction": "subtraction",
149
+ "multiply": "multiplication",
150
+ "multiplication": "multiplication",
151
+ "divide": "division",
152
+ "division": "division",
153
+ }[operation]
154
+ prefix = "Add" if re.search(r"\b(add|new|function|implement)\b", value, re.IGNORECASE) else "Implement"
155
+ return (
156
+ f"{prefix} a matrix {canonical_operation} function for two {dimension} matrices "
157
+ f"(matrix inputs)"
158
+ )
159
+
160
+ # Repair simple count+noun shorthand without inventing domain behavior.
161
+ value = re.sub(r"\b2\s+matrix\b", "two matrices", value, flags=re.IGNORECASE)
162
+ value = re.sub(r"\b2\s+file\b", "two files", value, flags=re.IGNORECASE)
163
+ value = re.sub(r"\b2\s+test\b", "two tests", value, flags=re.IGNORECASE)
164
+ return value
165
+
166
+
101
167
  def _section_header(line: str) -> tuple[str, str] | None:
102
168
  stripped = line.strip()
103
169
  markdown = re.match(r"^#{1,6}\s+(.+?)\s*$", stripped)
@@ -182,15 +248,21 @@ def _append_criterion(
182
248
 
183
249
 
184
250
  def compile_task(requirement: str) -> TaskSpec:
185
- goal = re.sub(r"\s+", " ", requirement).strip()
186
- if not goal:
251
+ raw_goal = re.sub(r"\s+", " ", requirement).strip()
252
+ if not raw_goal:
187
253
  raise ValueError("Engineering requirement cannot be empty")
254
+ goal = _normalize_terse_requirement(requirement)
188
255
  task_type = _classify(goal)
189
256
  code_change = task_type is not TaskType.UNIT_TEST or "only" not in goal.lower()
190
257
  requires_tests = task_type is not TaskType.BUILD_FAILURE
191
258
 
192
259
  criteria: list[AcceptanceCriterion] = []
193
- for item in _user_acceptance_items(requirement):
260
+ # Structured user requirements remain authoritative. Only an unstructured,
261
+ # terse prompt is compiled into the clearer canonical request.
262
+ user_items = _user_acceptance_items(requirement)
263
+ if len(user_items) == 1 and user_items[0] == raw_goal and goal != raw_goal:
264
+ user_items = [goal]
265
+ for item in user_items:
194
266
  _append_criterion(criteria, item, source=AcceptanceSource.USER)
195
267
 
196
268
  if task_type in {TaskType.BUG_FIX, TaskType.RUNTIME_ERROR, TaskType.TEST_FAILURE}:
@@ -240,9 +312,61 @@ def compile_task(requirement: str) -> TaskSpec:
240
312
  )
241
313
 
242
314
 
315
+ def _repository_language(repository: Any) -> str | None:
316
+ languages = [
317
+ language.lower()
318
+ for component in repository.components
319
+ for language in component.languages
320
+ ]
321
+ for preferred in ("python", "java", "csharp", "c#", "javascript", "typescript", "go", "rust"):
322
+ if preferred in languages:
323
+ return preferred
324
+ return languages[0] if languages else None
325
+
326
+
327
+ def _matrix_operation_contract(task: TaskSpec, repository: Any) -> None:
328
+ """Turn the bounded matrix shorthand compiler output into a repo-style callable contract."""
329
+
330
+ match = re.fullmatch(
331
+ r"(?:Add|Implement) a matrix (addition|subtraction|multiplication|division) "
332
+ r"function for two (\d+x\d+) matrices \(matrix inputs\)",
333
+ task.goal,
334
+ )
335
+ if match is None:
336
+ return
337
+
338
+ operation, dimension = match.groups()
339
+ verb = {
340
+ "addition": "add",
341
+ "subtraction": "subtract",
342
+ "multiplication": "multiply",
343
+ "division": "divide",
344
+ }[operation]
345
+ compact_dimension = dimension.replace("x", "x")
346
+ language = _repository_language(repository)
347
+ if language in {"java", "javascript", "typescript"}:
348
+ symbol = f"{verb}Matrices{compact_dimension}"
349
+ elif language in {"csharp", "c#"}:
350
+ symbol = f"{verb.capitalize()}Matrices{compact_dimension}"
351
+ else:
352
+ symbol = f"{verb}_matrices_{compact_dimension}"
353
+
354
+ compiled = (
355
+ f"Add {symbol}(a, b) to perform element-wise matrix {operation} "
356
+ f"for two {dimension} matrices"
357
+ )
358
+ task.goal = compiled
359
+ user_criteria = [
360
+ criterion for criterion in task.acceptance_criteria if criterion.source is AcceptanceSource.USER
361
+ ]
362
+ if len(user_criteria) == 1:
363
+ user_criteria[0].description = compiled
364
+
365
+
243
366
  def enrich_acceptance_contract(task: TaskSpec, repository: Any) -> TaskSpec:
244
- """Add checks derived from trusted repository capabilities without replacing user intent."""
367
+ """Compile safe repository-aware defaults, then add trusted repository checks."""
245
368
 
369
+ _matrix_operation_contract(task, repository)
246
370
  seen_commands: set[tuple[str, ...]] = set()
247
371
  for capability in repository.capabilities:
248
372
  if not capability.trusted:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devagent-ai
3
- Version: 0.8.1
3
+ Version: 0.8.2
4
4
  Summary: Evidence-driven local autonomous software engineering agent
5
5
  Author: Tom Ha
6
6
  Maintainer: Tom Ha
@@ -67,6 +67,7 @@ tests/test_preservation_contradiction.py
67
67
  tests/test_production_hardening.py
68
68
  tests/test_production_v040.py
69
69
  tests/test_realworld_benchmark.py
70
+ tests/test_requirement_compiler_v082.py
70
71
  tests/test_retrieval.py
71
72
  tests/test_runtime_sandbox.py
72
73
  tests/test_safety_workspace.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "devagent-ai"
7
- version = "0.8.1"
7
+ version = "0.8.2"
8
8
  description = "Evidence-driven local autonomous software engineering agent"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -62,7 +62,7 @@ def test_release_version_is_semver_and_consistent() -> None:
62
62
  version = match.group(1)
63
63
  assert re.fullmatch(r"\d+\.\d+\.\d+", version)
64
64
  assert version == __version__
65
- assert version == "0.8.1"
65
+ assert version == "0.8.2"
66
66
 
67
67
 
68
68
  def test_release_workflow_requires_green_exact_main_revision() -> None:
@@ -0,0 +1,185 @@
1
+ from __future__ import annotations
2
+
3
+ from devagent.models import (
4
+ AcceptanceSource,
5
+ AcceptanceStatus,
6
+ Capability,
7
+ CapabilityProvenance,
8
+ ChangeMetrics,
9
+ CodeSymbol,
10
+ Component,
11
+ DeveloperReviewEvidence,
12
+ Evidence,
13
+ RepositoryModel,
14
+ ReviewDecision,
15
+ TaskType,
16
+ Understanding,
17
+ VerificationResult,
18
+ )
19
+ from devagent.orchestrator import _support_acceptance_criteria
20
+ from devagent.tasking import compile_task, enrich_acceptance_contract
21
+
22
+
23
+ def _repo(language: str = "python") -> RepositoryModel:
24
+ return RepositoryModel(
25
+ root="/repo",
26
+ kind="single-component",
27
+ components=[
28
+ Component(
29
+ path=".",
30
+ languages=[language],
31
+ capabilities=[
32
+ Capability(
33
+ kind="test",
34
+ command=("python", "-m", "pytest", "-q"),
35
+ source="pyproject.toml",
36
+ provenance=CapabilityProvenance.EXPLICIT,
37
+ )
38
+ ],
39
+ )
40
+ ],
41
+ facts=[],
42
+ git_head="abc",
43
+ )
44
+
45
+
46
+ def _result(command: tuple[str, ...], *, tests: int | None = None) -> VerificationResult:
47
+ return VerificationResult(
48
+ command=command,
49
+ exit_code=0,
50
+ duration_seconds=0.1,
51
+ stdout="passed",
52
+ stderr="",
53
+ classification=None,
54
+ revision=2,
55
+ phase="final",
56
+ tests_run=tests,
57
+ tests_passed=tests,
58
+ )
59
+
60
+
61
+ def test_rough_matrix_prompt_becomes_precise_python_contract() -> None:
62
+ task = compile_task("add new function addition 2 matrix 2x2")
63
+ assert task.task_type is TaskType.FEATURE
64
+ assert task.goal == "Add a matrix addition function for two 2x2 matrices (matrix inputs)"
65
+
66
+ enrich_acceptance_contract(task, _repo())
67
+ assert task.goal == (
68
+ "Add add_matrices_2x2(a, b) to perform element-wise matrix addition "
69
+ "for two 2x2 matrices"
70
+ )
71
+ user = [item for item in task.acceptance_criteria if item.source is AcceptanceSource.USER]
72
+ assert [item.description for item in user] == [task.goal]
73
+
74
+
75
+ def test_requirement_compiler_preserves_explicit_user_callable() -> None:
76
+ requirement = "Add matrix_sum(a, b) for two 2x2 matrices"
77
+ task = compile_task(requirement)
78
+ enrich_acceptance_contract(task, _repo())
79
+ assert task.goal == requirement
80
+ user = [item for item in task.acceptance_criteria if item.source is AcceptanceSource.USER]
81
+ assert [item.description for item in user] == [requirement]
82
+
83
+
84
+ def test_common_typo_is_compiled_without_inventing_behavior() -> None:
85
+ task = compile_task("add new function substraction 2 matrix 2x2")
86
+ enrich_acceptance_contract(task, _repo())
87
+ assert task.goal == (
88
+ "Add subtract_matrices_2x2(a, b) to perform element-wise matrix subtraction "
89
+ "for two 2x2 matrices"
90
+ )
91
+ assert "mutation" not in task.goal.lower()
92
+ assert "invalid" not in task.goal.lower()
93
+
94
+
95
+ def test_repository_language_selects_conventional_java_callable() -> None:
96
+ task = compile_task("add new function addition 2 matrix 2x2")
97
+ enrich_acceptance_contract(task, _repo("java"))
98
+ assert "addMatrices2x2(a, b)" in task.goal
99
+
100
+
101
+ def test_compiled_matrix_contract_links_to_exact_final_symbol_and_tests() -> None:
102
+ task = compile_task("add new function addition 2 matrix 2x2")
103
+ repository = _repo()
104
+ enrich_acceptance_contract(task, repository)
105
+
106
+ pytest_result = _result(("python", "-m", "pytest", "-q"), tests=3)
107
+ diff_check = _result(("git", "diff", "--check"))
108
+ understanding = Understanding(
109
+ problem="Matrix addition is missing.",
110
+ expected_behavior="Add two 2x2 matrices element by element.",
111
+ affected_paths=["calculator.py", "test_calculator.py"],
112
+ root_cause="No matrix addition function exists.",
113
+ evidence=[Evidence("calculator.py contains only existing calculator operations.", ("calculator.py",), 1.0)],
114
+ proposed_solution=["Add the compiled matrix function and regression tests."],
115
+ confidence=1.0,
116
+ )
117
+ review_evidence = DeveloperReviewEvidence(
118
+ changed_symbols=[CodeSymbol("calculator.py", "add_matrices_2x2", "function", 5, "ADDED")],
119
+ test_cases=[
120
+ CodeSymbol("test_calculator.py", "test_add_matrices_2x2", "function", 8, "ADDED"),
121
+ CodeSymbol("test_calculator.py", "test_add_matrices_2x2_negative", "function", 12, "ADDED"),
122
+ ],
123
+ test_files=["test_calculator.py"],
124
+ )
125
+ _support_acceptance_criteria(
126
+ task,
127
+ repository,
128
+ understanding,
129
+ ChangeMetrics(2, 20, 0, ["calculator.py", "test_calculator.py"]),
130
+ [pytest_result, diff_check],
131
+ [pytest_result, diff_check],
132
+ ReviewDecision(True, [], "approved"),
133
+ review_evidence,
134
+ ["Added the compiled 2x2 matrix addition function and tests"],
135
+ "+def add_matrices_2x2(a, b):\n+ return [[a[r][c] + b[r][c] for c in range(2)] for r in range(2)]\n",
136
+ )
137
+
138
+ required = [item for item in task.acceptance_criteria if item.required]
139
+ assert required
140
+ assert all(item.status is AcceptanceStatus.SATISFIED for item in required)
141
+ user = [item for item in required if item.source is AcceptanceSource.USER]
142
+ assert user[0].evidence
143
+ assert any("add_matrices_2x2" in item for item in user[0].evidence)
144
+
145
+
146
+ def test_compiled_contract_does_not_accept_wrong_operation() -> None:
147
+ task = compile_task("add new function addition 2 matrix 2x2")
148
+ repository = _repo()
149
+ enrich_acceptance_contract(task, repository)
150
+
151
+ pytest_result = _result(("python", "-m", "pytest", "-q"), tests=3)
152
+ diff_check = _result(("git", "diff", "--check"))
153
+ understanding = Understanding(
154
+ problem="Matrix addition is missing.",
155
+ expected_behavior="Add two 2x2 matrices element by element.",
156
+ affected_paths=["calculator.py", "test_calculator.py"],
157
+ root_cause="No matrix addition function exists.",
158
+ evidence=[Evidence("calculator.py contains the current calculator implementation.", ("calculator.py",), 1.0)],
159
+ proposed_solution=["Add matrix behavior and tests."],
160
+ confidence=1.0,
161
+ )
162
+ wrong_review_evidence = DeveloperReviewEvidence(
163
+ changed_symbols=[CodeSymbol("calculator.py", "subtract_matrices_2x2", "function", 5, "ADDED")],
164
+ test_cases=[
165
+ CodeSymbol("test_calculator.py", "test_subtract_matrices_2x2", "function", 8, "ADDED"),
166
+ ],
167
+ test_files=["test_calculator.py"],
168
+ )
169
+ _support_acceptance_criteria(
170
+ task,
171
+ repository,
172
+ understanding,
173
+ ChangeMetrics(2, 12, 0, ["calculator.py", "test_calculator.py"]),
174
+ [pytest_result, diff_check],
175
+ [pytest_result, diff_check],
176
+ ReviewDecision(True, [], "approved"),
177
+ wrong_review_evidence,
178
+ ["Added a matrix function and tests"],
179
+ "+def subtract_matrices_2x2(a, b):\n+ return [[a[r][c] - b[r][c] for c in range(2)] for r in range(2)]\n",
180
+ )
181
+
182
+ user = [item for item in task.acceptance_criteria if item.source is AcceptanceSource.USER]
183
+ assert user[0].status is AcceptanceStatus.UNPROVEN
184
+ assert user[0].evidence == []
185
+ assert "semantically matches" in (user[0].reason or "")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes