algocode-agent 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.
Files changed (165) hide show
  1. algocode/__init__.py +5 -0
  2. algocode/__main__.py +4 -0
  3. algocode/acceptance/__init__.py +12 -0
  4. algocode/acceptance/matrix.py +69 -0
  5. algocode/acceptance/probe.py +206 -0
  6. algocode/acceptance/service.py +244 -0
  7. algocode/acceptance/types.py +58 -0
  8. algocode/application/__init__.py +1 -0
  9. algocode/application/services/__init__.py +25 -0
  10. algocode/application/services/apply_service.py +177 -0
  11. algocode/application/services/baseline_service.py +243 -0
  12. algocode/application/services/benchmark_service.py +628 -0
  13. algocode/application/services/candidate_service.py +169 -0
  14. algocode/application/services/contract_service.py +619 -0
  15. algocode/application/services/correctness_service.py +387 -0
  16. algocode/application/services/decision_service.py +228 -0
  17. algocode/application/services/project_bootstrap_service.py +891 -0
  18. algocode/application/services/project_service.py +125 -0
  19. algocode/application/services/project_state.py +219 -0
  20. algocode/application/services/report_service.py +323 -0
  21. algocode/application/services/task_service.py +77 -0
  22. algocode/approval/service.py +77 -0
  23. algocode/approval/types.py +29 -0
  24. algocode/benchmark/__init__.py +17 -0
  25. algocode/benchmark/engine.py +214 -0
  26. algocode/benchmark/environment.py +105 -0
  27. algocode/benchmark/spec.py +85 -0
  28. algocode/benchmark/types.py +128 -0
  29. algocode/bootstrap.py +268 -0
  30. algocode/cli/__init__.py +1 -0
  31. algocode/cli/commands/__init__.py +1 -0
  32. algocode/cli/commands/accept.py +72 -0
  33. algocode/cli/commands/api.py +220 -0
  34. algocode/cli/commands/apply.py +94 -0
  35. algocode/cli/commands/baseline.py +100 -0
  36. algocode/cli/commands/benchmark.py +227 -0
  37. algocode/cli/commands/candidate.py +155 -0
  38. algocode/cli/commands/correctness.py +197 -0
  39. algocode/cli/commands/diff.py +73 -0
  40. algocode/cli/commands/doctor.py +149 -0
  41. algocode/cli/commands/eval.py +115 -0
  42. algocode/cli/commands/experiment.py +151 -0
  43. algocode/cli/commands/gate.py +61 -0
  44. algocode/cli/commands/init.py +133 -0
  45. algocode/cli/commands/model.py +115 -0
  46. algocode/cli/commands/optimize.py +252 -0
  47. algocode/cli/commands/provider_test.py +90 -0
  48. algocode/cli/commands/report.py +59 -0
  49. algocode/cli/commands/retry.py +107 -0
  50. algocode/cli/commands/review.py +154 -0
  51. algocode/cli/commands/status.py +136 -0
  52. algocode/cli/commands/task.py +146 -0
  53. algocode/cli/context.py +86 -0
  54. algocode/cli/main.py +79 -0
  55. algocode/cli/output.py +70 -0
  56. algocode/config/__init__.py +6 -0
  57. algocode/config/global_file.py +54 -0
  58. algocode/config/loader.py +181 -0
  59. algocode/config/model.py +193 -0
  60. algocode/context/__init__.py +1 -0
  61. algocode/context/builder.py +410 -0
  62. algocode/context/estimator.py +25 -0
  63. algocode/context/facts.py +37 -0
  64. algocode/context/types.py +158 -0
  65. algocode/correctness/__init__.py +6 -0
  66. algocode/correctness/engine.py +452 -0
  67. algocode/correctness/protected.py +47 -0
  68. algocode/correctness/spec.py +108 -0
  69. algocode/correctness/types.py +45 -0
  70. algocode/domain/__init__.py +1 -0
  71. algocode/domain/commands/__init__.py +19 -0
  72. algocode/domain/errors/__init__.py +70 -0
  73. algocode/domain/events/__init__.py +5 -0
  74. algocode/domain/events/envelope.py +67 -0
  75. algocode/domain/model/__init__.py +76 -0
  76. algocode/domain/model/entities.py +151 -0
  77. algocode/domain/model/enums.py +119 -0
  78. algocode/domain/model/ids.py +36 -0
  79. algocode/domain/model/values.py +18 -0
  80. algocode/eval/__init__.py +1 -0
  81. algocode/eval/harness.py +377 -0
  82. algocode/eval/provider.py +135 -0
  83. algocode/eval/service.py +209 -0
  84. algocode/eval/suites.py +121 -0
  85. algocode/eval/types.py +76 -0
  86. algocode/languages/__init__.py +7 -0
  87. algocode/languages/cpp.py +288 -0
  88. algocode/languages/discovery.py +29 -0
  89. algocode/languages/python.py +94 -0
  90. algocode/languages/registry.py +60 -0
  91. algocode/languages/runner.py +68 -0
  92. algocode/languages/types.py +88 -0
  93. algocode/observability/__init__.py +1 -0
  94. algocode/policy/__init__.py +1 -0
  95. algocode/policy/engine.py +111 -0
  96. algocode/policy/types.py +44 -0
  97. algocode/ports/__init__.py +22 -0
  98. algocode/ports/artifact_store.py +22 -0
  99. algocode/ports/clock.py +12 -0
  100. algocode/ports/event_store.py +23 -0
  101. algocode/ports/language.py +30 -0
  102. algocode/ports/policy.py +7 -0
  103. algocode/ports/provider.py +12 -0
  104. algocode/ports/resource.py +7 -0
  105. algocode/ports/workspace.py +19 -0
  106. algocode/project_layout.py +135 -0
  107. algocode/providers/__init__.py +36 -0
  108. algocode/providers/anthropic.py +264 -0
  109. algocode/providers/errors.py +58 -0
  110. algocode/providers/factory.py +86 -0
  111. algocode/providers/fake.py +148 -0
  112. algocode/providers/openai_compatible.py +353 -0
  113. algocode/providers/types.py +101 -0
  114. algocode/report/__init__.py +1 -0
  115. algocode/resources/__init__.py +6 -0
  116. algocode/resources/local.py +235 -0
  117. algocode/resources/types.py +35 -0
  118. algocode/runtime/__init__.py +1 -0
  119. algocode/runtime/agent.py +2238 -0
  120. algocode/runtime/analysis.py +187 -0
  121. algocode/runtime/gates.py +69 -0
  122. algocode/runtime/locks.py +61 -0
  123. algocode/runtime/model_log.py +265 -0
  124. algocode/runtime/optimization_record.py +332 -0
  125. algocode/runtime/planning.py +73 -0
  126. algocode/runtime/repair_memory.py +195 -0
  127. algocode/runtime/task_lock.py +61 -0
  128. algocode/sandbox/local.py +71 -0
  129. algocode/sandbox/runner.py +393 -0
  130. algocode/security/__init__.py +6 -0
  131. algocode/security/credentials.py +75 -0
  132. algocode/security/redaction.py +83 -0
  133. algocode/storage/__init__.py +1 -0
  134. algocode/storage/artifacts/__init__.py +5 -0
  135. algocode/storage/artifacts/file_artifact_store.py +90 -0
  136. algocode/storage/events/__init__.py +5 -0
  137. algocode/storage/events/sqlite_event_store.py +135 -0
  138. algocode/storage/paths.py +27 -0
  139. algocode/storage/sqlite/__init__.py +5 -0
  140. algocode/storage/sqlite/approval_store.py +65 -0
  141. algocode/storage/sqlite/database.py +51 -0
  142. algocode/storage/sqlite/migrations.py +298 -0
  143. algocode/storage/sqlite/projections/__init__.py +12 -0
  144. algocode/storage/sqlite/projections/baseline_projection.py +137 -0
  145. algocode/storage/sqlite/projections/benchmark_projection.py +210 -0
  146. algocode/storage/sqlite/projections/candidate_projection.py +177 -0
  147. algocode/storage/sqlite/projections/correctness_projection.py +148 -0
  148. algocode/storage/sqlite/projections/decision_projection.py +95 -0
  149. algocode/storage/sqlite/projections/project_projection.py +84 -0
  150. algocode/storage/sqlite/projections/task_projection.py +194 -0
  151. algocode/structured_output.py +153 -0
  152. algocode/tools/__init__.py +26 -0
  153. algocode/tools/builtins/__init__.py +1 -0
  154. algocode/tools/builtins/actions.py +707 -0
  155. algocode/tools/builtins/filesystem.py +459 -0
  156. algocode/tools/registry.py +201 -0
  157. algocode/tools/types.py +57 -0
  158. algocode/workspace/__init__.py +12 -0
  159. algocode/workspace/git.py +470 -0
  160. algocode/workspace/types.py +31 -0
  161. algocode_agent-0.1.0.dist-info/METADATA +678 -0
  162. algocode_agent-0.1.0.dist-info/RECORD +165 -0
  163. algocode_agent-0.1.0.dist-info/WHEEL +4 -0
  164. algocode_agent-0.1.0.dist-info/entry_points.txt +2 -0
  165. algocode_agent-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,177 @@
1
+ """Apply accepted candidates to the user workspace and roll them back."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from uuid import uuid4
7
+
8
+ from algocode.application.services.baseline_service import BaselineService
9
+ from algocode.application.services.candidate_service import CandidateService
10
+ from algocode.application.services.decision_service import DecisionService
11
+ from algocode.application.services.project_service import ProjectService
12
+ from algocode.application.services.task_service import TaskService
13
+ from algocode.domain.errors import DecisionError
14
+ from algocode.domain.events import EventEnvelope, EventType
15
+ from algocode.domain.model import CandidateId, CandidateStatus, DecisionOutcome, TaskId
16
+ from algocode.ports import ArtifactStore, EventStore
17
+ from algocode.workspace import GitRepository, GitWorkspaceManager
18
+
19
+
20
+ class ApplyService:
21
+ """Apply or roll back accepted candidates with stale checks."""
22
+
23
+ def __init__(
24
+ self,
25
+ event_store: EventStore,
26
+ task_service: TaskService,
27
+ project_service: ProjectService,
28
+ baseline_service: BaselineService,
29
+ candidate_service: CandidateService,
30
+ decision_service: DecisionService,
31
+ artifact_store: ArtifactStore,
32
+ data_dir: str | Path,
33
+ ) -> None:
34
+ self._event_store = event_store
35
+ self._task_service = task_service
36
+ self._project_service = project_service
37
+ self._baseline_service = baseline_service
38
+ self._candidate_service = candidate_service
39
+ self._decision_service = decision_service
40
+ self._artifact_store = artifact_store
41
+ self._data_dir = Path(data_dir)
42
+
43
+ async def apply(
44
+ self,
45
+ task_id: TaskId | str,
46
+ candidate_id: CandidateId | str,
47
+ ) -> dict[str, object]:
48
+ task = await self._task_service.get_task(task_id)
49
+ candidate = await self._candidate_service.get(candidate_id)
50
+ if candidate.task_id != task.id:
51
+ raise DecisionError("candidate does not belong to the task")
52
+ decision = await self._decision_service.get_for_candidate(candidate.id)
53
+ if decision is None or decision.outcome is not DecisionOutcome.ACCEPTED:
54
+ raise DecisionError("candidate must be accepted before apply")
55
+ if candidate.status is CandidateStatus.APPLIED:
56
+ raise DecisionError("candidate is already applied")
57
+ baseline = await self._baseline_service.get_for_task(task.id)
58
+ if baseline is None:
59
+ raise DecisionError("task baseline is required before apply")
60
+ apply_base_revision = (
61
+ candidate.apply_base_revision.value
62
+ if candidate.apply_base_revision is not None
63
+ else baseline.revision.value
64
+ )
65
+ apply_base_snapshot_hash = candidate.apply_base_snapshot_hash or baseline.snapshot_hash
66
+ project = await self._project_service.get(task.project_id)
67
+ repository = await GitRepository.discover(project.root_path)
68
+ current = await repository.capture_snapshot(repository.root)
69
+ if (
70
+ current.revision != apply_base_revision
71
+ or current.snapshot_hash != apply_base_snapshot_hash
72
+ ):
73
+ await self._mark_stale(task.id, candidate.id)
74
+ raise DecisionError("candidate is stale; re-verify before apply")
75
+ candidate_snapshot = await repository.capture_snapshot(Path(candidate.workspace_ref))
76
+ patch_ref = await self._artifact_store.put(
77
+ candidate_snapshot.patch,
78
+ kind="candidate-patch",
79
+ mime_type="application/x-git-patch",
80
+ metadata={"task_id": str(task.id), "candidate_id": str(candidate.id)},
81
+ )
82
+ manager = GitWorkspaceManager(repository, self._data_dir)
83
+ workspace = await self._candidate_service.workspace(candidate.id)
84
+ result = await manager.apply(workspace)
85
+ if not result.applied:
86
+ raise DecisionError(result.message or "candidate apply failed")
87
+ applied_snapshot = await repository.capture_snapshot(repository.root)
88
+ seq = await self._next_seq(str(task.id))
89
+ await self._event_store.append(
90
+ str(task.id),
91
+ seq - 1,
92
+ (
93
+ _event(
94
+ str(task.id),
95
+ seq,
96
+ EventType.CANDIDATE_APPLIED,
97
+ {
98
+ "candidate_id": str(candidate.id),
99
+ "workspace_id": workspace.id,
100
+ "reverse_patch_ref": _artifact_payload(patch_ref),
101
+ "applied_snapshot_hash": applied_snapshot.snapshot_hash,
102
+ },
103
+ ),
104
+ ),
105
+ )
106
+ return {
107
+ "candidate_id": str(candidate.id),
108
+ "applied": True,
109
+ "patch_ref": _artifact_payload(patch_ref),
110
+ }
111
+
112
+ async def rollback(
113
+ self,
114
+ task_id: TaskId | str,
115
+ candidate_id: CandidateId | str,
116
+ ) -> dict[str, object]:
117
+ task = await self._task_service.get_task(task_id)
118
+ candidate = await self._candidate_service.get(candidate_id)
119
+ if candidate.status is not CandidateStatus.APPLIED:
120
+ raise DecisionError("candidate is not applied")
121
+ project = await self._project_service.get(task.project_id)
122
+ repository = await GitRepository.discover(project.root_path)
123
+ manager = GitWorkspaceManager(repository, self._data_dir)
124
+ workspace = await self._candidate_service.workspace(candidate.id)
125
+ await manager.rollback(workspace)
126
+ seq = await self._next_seq(str(task.id))
127
+ await self._event_store.append(
128
+ str(task.id),
129
+ seq - 1,
130
+ (
131
+ _event(
132
+ str(task.id),
133
+ seq,
134
+ EventType.CANDIDATE_ROLLED_BACK,
135
+ {"candidate_id": str(candidate.id), "workspace_id": workspace.id},
136
+ ),
137
+ ),
138
+ )
139
+ return {"candidate_id": str(candidate.id), "rolled_back": True}
140
+
141
+ async def _mark_stale(self, task_id: TaskId, candidate_id: CandidateId) -> None:
142
+ seq = await self._next_seq(str(task_id))
143
+ await self._event_store.append(
144
+ str(task_id),
145
+ seq - 1,
146
+ (
147
+ _event(
148
+ str(task_id),
149
+ seq,
150
+ EventType.CANDIDATE_REJECTED,
151
+ {"candidate_id": str(candidate_id), "status": CandidateStatus.STALE.value},
152
+ ),
153
+ ),
154
+ )
155
+
156
+ async def _next_seq(self, aggregate_id: str) -> int:
157
+ events = await self._event_store.read(aggregate_id)
158
+ return events[-1].seq + 1 if events else 1
159
+
160
+
161
+ def _event(
162
+ aggregate_id: str,
163
+ seq: int,
164
+ event_type: EventType,
165
+ payload: dict[str, object],
166
+ ) -> EventEnvelope:
167
+ return EventEnvelope(
168
+ id=f"evt_{uuid4().hex}",
169
+ aggregate_id=aggregate_id,
170
+ seq=seq,
171
+ type=event_type,
172
+ payload=payload,
173
+ )
174
+
175
+
176
+ def _artifact_payload(ref) -> dict[str, str]:
177
+ return {"uri": ref.uri, "sha256": ref.sha256}
@@ -0,0 +1,243 @@
1
+ """Baseline capture orchestration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from collections.abc import Callable
7
+ from datetime import UTC, datetime
8
+ from uuid import uuid4
9
+
10
+ from algocode.application.services.project_service import ProjectService
11
+ from algocode.application.services.task_service import TaskService
12
+ from algocode.benchmark.environment import compute_environment_hash
13
+ from algocode.domain.errors import BaselineBuildFailed, BaselineError, NotFoundError
14
+ from algocode.domain.events import EventEnvelope, EventType
15
+ from algocode.domain.model import Baseline, GitRevision, Language, TaskId
16
+ from algocode.languages import LanguageRegistry
17
+ from algocode.languages.types import BuildProfile, BuildResult
18
+ from algocode.ports import ArtifactStore, EventStore
19
+ from algocode.storage.sqlite.database import Database
20
+ from algocode.storage.sqlite.projections.baseline_projection import BaselineProjection
21
+ from algocode.workspace import GitRepository, GitWorkspaceManager
22
+
23
+
24
+ class BaselineService:
25
+ """Prepare an isolated workspace, build it, and persist a baseline."""
26
+
27
+ def __init__(
28
+ self,
29
+ event_store: EventStore,
30
+ database: Database,
31
+ task_service: TaskService,
32
+ project_service: ProjectService,
33
+ baseline_projection: BaselineProjection,
34
+ language_registry: LanguageRegistry,
35
+ artifact_store: ArtifactStore,
36
+ workspace_manager_factory: Callable[[GitRepository], GitWorkspaceManager],
37
+ ) -> None:
38
+ self._event_store = event_store
39
+ self._database = database
40
+ self._task_service = task_service
41
+ self._project_service = project_service
42
+ self._baseline_projection = baseline_projection
43
+ self._language_registry = language_registry
44
+ self._artifact_store = artifact_store
45
+ self._workspace_manager_factory = workspace_manager_factory
46
+
47
+ async def capture(
48
+ self,
49
+ task_id: TaskId | str,
50
+ *,
51
+ language: Language | str = Language.AUTO,
52
+ source_root: str = ".",
53
+ commands: tuple[tuple[str, ...], ...] = (),
54
+ timeout_seconds: int = 120,
55
+ ) -> Baseline:
56
+ task = await self._task_service.get_task(task_id)
57
+ project = await self._project_service.get(task.project_id)
58
+ repository = await GitRepository.discover(project.root_path)
59
+ revision = await repository.head_revision()
60
+ started_seq = await self._next_seq(str(task.id))
61
+ await self._event_store.append(
62
+ str(task.id),
63
+ started_seq - 1,
64
+ (
65
+ _event(
66
+ str(task.id),
67
+ started_seq,
68
+ EventType.BASELINE_STARTED,
69
+ {"revision": revision, "project_id": str(project.id)},
70
+ ),
71
+ ),
72
+ )
73
+
74
+ workspace_manager = self._workspace_manager_factory(repository)
75
+ try:
76
+ workspace = await workspace_manager.prepare_baseline(str(task.id), revision)
77
+ detected = await self._language_registry.detect(workspace.path)
78
+ requested_language = Language(language)
79
+ if requested_language is Language.AUTO:
80
+ requested_language = (
81
+ project.language if project.language is not Language.AUTO else Language.AUTO
82
+ )
83
+ selected_language = self._language_registry.resolve_language(
84
+ requested_language,
85
+ detected,
86
+ )
87
+ adapter = self._language_registry.adapter_for(selected_language)
88
+ profile = BuildProfile(
89
+ commands=commands,
90
+ source_root=source_root,
91
+ timeout_seconds=timeout_seconds,
92
+ )
93
+ await adapter.prepare(workspace.path, profile)
94
+ build_result = await adapter.build(workspace.path, profile)
95
+ except Exception as exc:
96
+ failed_seq = started_seq + 1
97
+ await self._event_store.append(
98
+ str(task.id),
99
+ started_seq,
100
+ (
101
+ _event(
102
+ str(task.id),
103
+ failed_seq,
104
+ EventType.BASELINE_FAILED,
105
+ {
106
+ "revision": revision,
107
+ "reason": str(exc),
108
+ },
109
+ ),
110
+ ),
111
+ )
112
+ if isinstance(exc, BaselineError):
113
+ raise
114
+ raise BaselineError(str(exc)) from exc
115
+
116
+ output_ref = await self._artifact_store.put(
117
+ build_result.output,
118
+ kind="baseline-build-output",
119
+ mime_type="text/plain",
120
+ metadata={"task_id": str(task.id), "revision": revision},
121
+ )
122
+ metadata_ref = await self._artifact_store.put(
123
+ _build_metadata(build_result),
124
+ kind="baseline-build-result",
125
+ mime_type="application/json",
126
+ metadata={"task_id": str(task.id), "revision": revision},
127
+ )
128
+ if not build_result.succeeded:
129
+ failed_seq = started_seq + 1
130
+ await self._event_store.append(
131
+ str(task.id),
132
+ started_seq,
133
+ (
134
+ _event(
135
+ str(task.id),
136
+ failed_seq,
137
+ EventType.BASELINE_FAILED,
138
+ {
139
+ "revision": revision,
140
+ "reason": f"baseline build exited with {build_result.exit_code}",
141
+ "build_result_ref": _artifact_payload(output_ref),
142
+ },
143
+ artifact_refs=(output_ref, metadata_ref),
144
+ ),
145
+ ),
146
+ )
147
+ raise BaselineBuildFailed(f"baseline build exited with code {build_result.exit_code}")
148
+
149
+ now = datetime.now(UTC)
150
+ baseline = Baseline(
151
+ id=f"base_{uuid4().hex}",
152
+ task_id=task.id,
153
+ revision=GitRevision(revision),
154
+ snapshot_hash=workspace.base_snapshot_hash,
155
+ environment_hash=compute_environment_hash(workspace.path),
156
+ build_result_ref=output_ref,
157
+ workspace_ref=str(workspace.path),
158
+ created_at=now,
159
+ )
160
+ captured_seq = started_seq + 1
161
+ await self._event_store.append(
162
+ str(task.id),
163
+ started_seq,
164
+ (
165
+ _event(
166
+ str(task.id),
167
+ captured_seq,
168
+ EventType.BASELINE_CAPTURED,
169
+ {
170
+ "baseline_id": baseline.id,
171
+ "revision": baseline.revision.value,
172
+ "snapshot_hash": baseline.snapshot_hash,
173
+ "environment_hash": baseline.environment_hash,
174
+ "build_result_ref": _artifact_payload(output_ref),
175
+ "correctness_result_ref": None,
176
+ "benchmark_result_ref": None,
177
+ "created_at": baseline.created_at.isoformat(),
178
+ "workspace_ref": str(workspace.path),
179
+ },
180
+ artifact_refs=(output_ref, metadata_ref),
181
+ ),
182
+ ),
183
+ )
184
+ return baseline
185
+
186
+ async def get(self, baseline_id: str) -> Baseline:
187
+ with self._database.connect() as connection:
188
+ baseline = self._baseline_projection.get(connection, baseline_id)
189
+ if baseline is None:
190
+ raise NotFoundError(f"baseline {baseline_id} was not found")
191
+ return baseline
192
+
193
+ async def get_for_task(self, task_id: TaskId | str) -> Baseline | None:
194
+ with self._database.connect() as connection:
195
+ return self._baseline_projection.get_for_task(connection, str(task_id))
196
+
197
+ async def _next_seq(self, aggregate_id: str) -> int:
198
+ events = await self._event_store.read(aggregate_id)
199
+ return events[-1].seq + 1 if events else 1
200
+
201
+
202
+ def _event(
203
+ aggregate_id: str,
204
+ seq: int,
205
+ event_type: EventType,
206
+ payload: dict[str, object],
207
+ *,
208
+ artifact_refs=(),
209
+ ) -> EventEnvelope:
210
+ return EventEnvelope(
211
+ id=f"evt_{uuid4().hex}",
212
+ aggregate_id=aggregate_id,
213
+ seq=seq,
214
+ type=event_type,
215
+ payload=payload,
216
+ artifact_refs=tuple(artifact_refs),
217
+ )
218
+
219
+
220
+ def _artifact_payload(ref) -> dict[str, str]:
221
+ return {"uri": ref.uri, "sha256": ref.sha256}
222
+
223
+
224
+ def _build_metadata(result: BuildResult) -> bytes:
225
+ payload = {
226
+ "language": result.language.value,
227
+ "commands": [list(command) for command in result.commands],
228
+ "run_command": list(result.run_command),
229
+ "executable": str(result.executable) if result.executable is not None else None,
230
+ "exit_code": result.exit_code,
231
+ "duration_seconds": result.duration_seconds,
232
+ "diagnostics": [
233
+ {
234
+ "severity": diagnostic.severity,
235
+ "message": diagnostic.message,
236
+ "file": diagnostic.file,
237
+ "line": diagnostic.line,
238
+ "column": diagnostic.column,
239
+ }
240
+ for diagnostic in result.diagnostics
241
+ ],
242
+ }
243
+ return json.dumps(payload, ensure_ascii=False, sort_keys=True).encode("utf-8")