devcouncil 0.2.0 → 0.2.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 (205) hide show
  1. package/README.md +144 -308
  2. package/bin/devcouncil.js +130 -32
  3. package/package.json +22 -19
  4. package/pyproject.toml +0 -66
  5. package/src/devcouncil/__init__.py +0 -0
  6. package/src/devcouncil/__main__.py +0 -4
  7. package/src/devcouncil/app/__init__.py +0 -28
  8. package/src/devcouncil/app/config.py +0 -320
  9. package/src/devcouncil/app/errors.py +0 -23
  10. package/src/devcouncil/app/events.py +0 -44
  11. package/src/devcouncil/app/orchestrator.py +0 -92
  12. package/src/devcouncil/app/project_status.py +0 -29
  13. package/src/devcouncil/app/run_context.py +0 -39
  14. package/src/devcouncil/app/state_machine.py +0 -108
  15. package/src/devcouncil/artifacts/__init__.py +0 -1
  16. package/src/devcouncil/artifacts/coverage.py +0 -96
  17. package/src/devcouncil/artifacts/graph.py +0 -163
  18. package/src/devcouncil/artifacts/migrations.py +0 -20
  19. package/src/devcouncil/artifacts/schemas.py +0 -23
  20. package/src/devcouncil/artifacts/serializer.py +0 -21
  21. package/src/devcouncil/artifacts/validators.py +0 -27
  22. package/src/devcouncil/assets/__init__.py +0 -1
  23. package/src/devcouncil/assets/devcouncil-logo.svg +0 -60
  24. package/src/devcouncil/assets/devcouncil_logo_premium.png +0 -0
  25. package/src/devcouncil/cli/__init__.py +0 -0
  26. package/src/devcouncil/cli/commands/__init__.py +0 -0
  27. package/src/devcouncil/cli/commands/agents.py +0 -292
  28. package/src/devcouncil/cli/commands/artifacts.py +0 -54
  29. package/src/devcouncil/cli/commands/ast.py +0 -22
  30. package/src/devcouncil/cli/commands/baseline.py +0 -35
  31. package/src/devcouncil/cli/commands/check.py +0 -209
  32. package/src/devcouncil/cli/commands/config.py +0 -115
  33. package/src/devcouncil/cli/commands/cost.py +0 -57
  34. package/src/devcouncil/cli/commands/dashboard.py +0 -31
  35. package/src/devcouncil/cli/commands/doctor.py +0 -340
  36. package/src/devcouncil/cli/commands/evidence.py +0 -48
  37. package/src/devcouncil/cli/commands/go.py +0 -656
  38. package/src/devcouncil/cli/commands/handoff.py +0 -69
  39. package/src/devcouncil/cli/commands/hook.py +0 -237
  40. package/src/devcouncil/cli/commands/init.py +0 -289
  41. package/src/devcouncil/cli/commands/integrate.py +0 -1515
  42. package/src/devcouncil/cli/commands/lsp.py +0 -20
  43. package/src/devcouncil/cli/commands/map.py +0 -112
  44. package/src/devcouncil/cli/commands/mcp_server.py +0 -18
  45. package/src/devcouncil/cli/commands/plan.py +0 -488
  46. package/src/devcouncil/cli/commands/prompt.py +0 -61
  47. package/src/devcouncil/cli/commands/repair.py +0 -89
  48. package/src/devcouncil/cli/commands/report.py +0 -137
  49. package/src/devcouncil/cli/commands/reset_demo_state.py +0 -33
  50. package/src/devcouncil/cli/commands/rollback.py +0 -59
  51. package/src/devcouncil/cli/commands/run.py +0 -289
  52. package/src/devcouncil/cli/commands/runs.py +0 -223
  53. package/src/devcouncil/cli/commands/scaffold.py +0 -32
  54. package/src/devcouncil/cli/commands/semantic.py +0 -47
  55. package/src/devcouncil/cli/commands/setup.py +0 -362
  56. package/src/devcouncil/cli/commands/shell.py +0 -73
  57. package/src/devcouncil/cli/commands/show.py +0 -76
  58. package/src/devcouncil/cli/commands/skills.py +0 -88
  59. package/src/devcouncil/cli/commands/status.py +0 -141
  60. package/src/devcouncil/cli/commands/tasks.py +0 -55
  61. package/src/devcouncil/cli/commands/trace.py +0 -88
  62. package/src/devcouncil/cli/commands/verify.py +0 -328
  63. package/src/devcouncil/cli/commands/version.py +0 -20
  64. package/src/devcouncil/cli/commands/watch.py +0 -574
  65. package/src/devcouncil/cli/commands/watch_fs.py +0 -40
  66. package/src/devcouncil/cli/main.py +0 -137
  67. package/src/devcouncil/council/__init__.py +0 -0
  68. package/src/devcouncil/council/prompts/__init__.py +0 -0
  69. package/src/devcouncil/council/prompts/arbiter.md +0 -19
  70. package/src/devcouncil/council/prompts/critic_a.md +0 -10
  71. package/src/devcouncil/council/prompts/critic_b.md +0 -10
  72. package/src/devcouncil/council/prompts/implementation_reviewer.md +0 -16
  73. package/src/devcouncil/council/prompts/planner_a.md +0 -16
  74. package/src/devcouncil/council/prompts/planner_b.md +0 -16
  75. package/src/devcouncil/council/prompts/rebuttal.md +0 -10
  76. package/src/devcouncil/council/prompts/spec_writer.md +0 -12
  77. package/src/devcouncil/domain/__init__.py +0 -0
  78. package/src/devcouncil/domain/assumption.py +0 -17
  79. package/src/devcouncil/domain/critique.py +0 -32
  80. package/src/devcouncil/domain/evidence.py +0 -47
  81. package/src/devcouncil/domain/gap.py +0 -52
  82. package/src/devcouncil/domain/requirement.py +0 -22
  83. package/src/devcouncil/domain/task.py +0 -55
  84. package/src/devcouncil/execution/__init__.py +0 -1
  85. package/src/devcouncil/execution/checkpoints.py +0 -246
  86. package/src/devcouncil/execution/context_builder.py +0 -60
  87. package/src/devcouncil/execution/executor.py +0 -15
  88. package/src/devcouncil/execution/fs_watcher.py +0 -180
  89. package/src/devcouncil/execution/handoff.py +0 -102
  90. package/src/devcouncil/execution/hook_policy.py +0 -253
  91. package/src/devcouncil/execution/patch.py +0 -77
  92. package/src/devcouncil/execution/paths.py +0 -14
  93. package/src/devcouncil/execution/permissions.py +0 -85
  94. package/src/devcouncil/execution/policy_engine.py +0 -343
  95. package/src/devcouncil/execution/prompt_builder.py +0 -671
  96. package/src/devcouncil/execution/shell_session.py +0 -225
  97. package/src/devcouncil/execution/task_runner.py +0 -170
  98. package/src/devcouncil/executors/__init__.py +0 -1
  99. package/src/devcouncil/executors/agent_registry.py +0 -575
  100. package/src/devcouncil/executors/coding_cli.py +0 -736
  101. package/src/devcouncil/executors/mini_swe.py +0 -73
  102. package/src/devcouncil/executors/native/__init__.py +0 -0
  103. package/src/devcouncil/executors/native/agent.py +0 -208
  104. package/src/devcouncil/executors/openhands.py +0 -71
  105. package/src/devcouncil/gating/__init__.py +0 -1
  106. package/src/devcouncil/gating/checks/__init__.py +0 -0
  107. package/src/devcouncil/gating/checks/clean_git.py +0 -52
  108. package/src/devcouncil/gating/checks/planned_files_check.py +0 -32
  109. package/src/devcouncil/gating/checks/requirement_coverage.py +0 -26
  110. package/src/devcouncil/gating/checks/secret_scan_check.py +0 -53
  111. package/src/devcouncil/gating/policy.py +0 -338
  112. package/src/devcouncil/hardware.py +0 -184
  113. package/src/devcouncil/indexing/__init__.py +0 -1
  114. package/src/devcouncil/indexing/ast_matcher.py +0 -168
  115. package/src/devcouncil/indexing/graph_index.py +0 -48
  116. package/src/devcouncil/indexing/lsp.py +0 -161
  117. package/src/devcouncil/indexing/repo_mapper.py +0 -1455
  118. package/src/devcouncil/indexing/semantic_index.py +0 -205
  119. package/src/devcouncil/integrations/actions.py +0 -146
  120. package/src/devcouncil/integrations/check.py +0 -423
  121. package/src/devcouncil/integrations/code_review_graph.py +0 -163
  122. package/src/devcouncil/integrations/github.py +0 -39
  123. package/src/devcouncil/integrations/github_intent.py +0 -142
  124. package/src/devcouncil/integrations/gitnexus.py +0 -62
  125. package/src/devcouncil/integrations/graphify.py +0 -34
  126. package/src/devcouncil/integrations/mcp/__init__.py +0 -0
  127. package/src/devcouncil/integrations/mcp/server.py +0 -2122
  128. package/src/devcouncil/integrations/opencode_devcouncil_plugin.mjs +0 -24
  129. package/src/devcouncil/integrations/pr_comments.py +0 -62
  130. package/src/devcouncil/live/__init__.py +0 -2
  131. package/src/devcouncil/live/cards.py +0 -349
  132. package/src/devcouncil/live/models.py +0 -63
  133. package/src/devcouncil/live/repair_prompt.py +0 -83
  134. package/src/devcouncil/live/reviewer.py +0 -70
  135. package/src/devcouncil/live/signals.py +0 -135
  136. package/src/devcouncil/live/summary.py +0 -34
  137. package/src/devcouncil/live/tasks.py +0 -18
  138. package/src/devcouncil/live/transcripts.py +0 -141
  139. package/src/devcouncil/llm/__init__.py +0 -1
  140. package/src/devcouncil/llm/cache.py +0 -42
  141. package/src/devcouncil/llm/model_defaults.yaml +0 -44
  142. package/src/devcouncil/llm/provider.py +0 -627
  143. package/src/devcouncil/llm/router.py +0 -310
  144. package/src/devcouncil/optimization/__init__.py +0 -1
  145. package/src/devcouncil/optimization/gepa_agent.py +0 -318
  146. package/src/devcouncil/planning/__init__.py +0 -1
  147. package/src/devcouncil/planning/arbiter_service.py +0 -57
  148. package/src/devcouncil/planning/correction_manifest.py +0 -303
  149. package/src/devcouncil/planning/critique_service.py +0 -71
  150. package/src/devcouncil/planning/plan_service.py +0 -60
  151. package/src/devcouncil/planning/prompt_enhancer_service.py +0 -167
  152. package/src/devcouncil/planning/repair_service.py +0 -39
  153. package/src/devcouncil/planning/spec_service.py +0 -70
  154. package/src/devcouncil/repo/__init__.py +0 -0
  155. package/src/devcouncil/repo/ci_scaffold.py +0 -157
  156. package/src/devcouncil/repo/gitignore.py +0 -123
  157. package/src/devcouncil/repo/sca.py +0 -374
  158. package/src/devcouncil/reporting/__init__.py +0 -0
  159. package/src/devcouncil/reporting/github_check.py +0 -32
  160. package/src/devcouncil/reporting/json_report.py +0 -30
  161. package/src/devcouncil/reporting/markdown_report.py +0 -83
  162. package/src/devcouncil/reporting/report_builder.py +0 -14
  163. package/src/devcouncil/skills/__init__.py +0 -19
  164. package/src/devcouncil/skills/library/README.md +0 -46
  165. package/src/devcouncil/skills/library/ai-training.md +0 -50
  166. package/src/devcouncil/skills/library/android.md +0 -50
  167. package/src/devcouncil/skills/library/backend.md +0 -52
  168. package/src/devcouncil/skills/library/core-engineering.md +0 -95
  169. package/src/devcouncil/skills/library/data-engineering.md +0 -47
  170. package/src/devcouncil/skills/library/desktop.md +0 -46
  171. package/src/devcouncil/skills/library/devops.md +0 -48
  172. package/src/devcouncil/skills/library/game-dev.md +0 -46
  173. package/src/devcouncil/skills/library/ios.md +0 -48
  174. package/src/devcouncil/skills/library/mobile-cross-platform.md +0 -46
  175. package/src/devcouncil/skills/library/security.md +0 -48
  176. package/src/devcouncil/skills/library/systems.md +0 -48
  177. package/src/devcouncil/skills/library/web.md +0 -47
  178. package/src/devcouncil/skills/library/windows.md +0 -47
  179. package/src/devcouncil/skills/registry.py +0 -330
  180. package/src/devcouncil/storage/__init__.py +0 -0
  181. package/src/devcouncil/storage/db.py +0 -147
  182. package/src/devcouncil/storage/models.py +0 -204
  183. package/src/devcouncil/storage/native.py +0 -557
  184. package/src/devcouncil/storage/repositories.py +0 -485
  185. package/src/devcouncil/telemetry/__init__.py +0 -0
  186. package/src/devcouncil/telemetry/cost.py +0 -140
  187. package/src/devcouncil/telemetry/model_pricing.yaml +0 -48
  188. package/src/devcouncil/telemetry/pricing.py +0 -28
  189. package/src/devcouncil/telemetry/traces.py +0 -146
  190. package/src/devcouncil/telemetry/tracker.py +0 -52
  191. package/src/devcouncil/ui/__init__.py +0 -1
  192. package/src/devcouncil/ui/dashboard.py +0 -423
  193. package/src/devcouncil/utils/__init__.py +0 -1
  194. package/src/devcouncil/utils/redaction.py +0 -147
  195. package/src/devcouncil/utils/subprocess_env.py +0 -69
  196. package/src/devcouncil/verification/__init__.py +0 -1
  197. package/src/devcouncil/verification/acceptance_compiler.py +0 -125
  198. package/src/devcouncil/verification/ad_hoc_check.py +0 -129
  199. package/src/devcouncil/verification/diff_coverage.py +0 -353
  200. package/src/devcouncil/verification/implementation_reviewer.py +0 -55
  201. package/src/devcouncil/verification/next_actions.py +0 -189
  202. package/src/devcouncil/verification/sandbox.py +0 -178
  203. package/src/devcouncil/verification/test_resolver.py +0 -91
  204. package/src/devcouncil/verification/verifier.py +0 -1548
  205. package/uv.lock +0 -1226
@@ -1,557 +0,0 @@
1
- """Native control-plane storage repositories."""
2
-
3
- from __future__ import annotations
4
-
5
- import json
6
- import secrets
7
- import uuid
8
- from datetime import datetime, timedelta, timezone
9
- from typing import Optional
10
-
11
- from pydantic import BaseModel
12
- from sqlalchemy.exc import IntegrityError
13
- from sqlmodel import Session, col, select
14
-
15
- from devcouncil.storage.models import (
16
- AgentHandoffModel,
17
- CorrectionManifestModel,
18
- FileChangeEventModel,
19
- SemanticDiffModel,
20
- ShellCommandEventModel,
21
- ShellSessionModel,
22
- TaskLeaseModel,
23
- VerificationRunModel,
24
- )
25
-
26
-
27
- def _utc_now() -> str:
28
- return datetime.now(timezone.utc).isoformat()
29
-
30
-
31
- class TaskLeaseRecord(BaseModel):
32
- id: str
33
- task_id: str
34
- owner: str
35
- agent: Optional[str] = None
36
- client_id: Optional[str] = None
37
- run_id: Optional[str] = None
38
- branch: Optional[str] = None
39
- lease_token: str
40
- status: str
41
- created_at: str
42
- expires_at: Optional[str] = None
43
- released_at: Optional[str] = None
44
-
45
-
46
- class ShellSessionRecord(BaseModel):
47
- id: str
48
- task_id: str
49
- lease_id: Optional[str] = None
50
- shell: str
51
- cwd: str
52
- status: str
53
- started_at: str
54
- ended_at: Optional[str] = None
55
-
56
-
57
- class ShellCommandRecord(BaseModel):
58
- id: int
59
- task_id: str
60
- session_id: Optional[str] = None
61
- command: str
62
- status: str
63
- exit_code: Optional[int] = None
64
- reason: str = ""
65
- stdout_path: str = ""
66
- stderr_path: str = ""
67
- created_at: str
68
-
69
-
70
- class FileChangeRecord(BaseModel):
71
- id: int
72
- task_id: Optional[str] = None
73
- lease_id: Optional[str] = None
74
- session_id: Optional[str] = None
75
- path: str
76
- operation: str
77
- allowed: bool
78
- reason: str = ""
79
- created_at: str
80
-
81
-
82
- class SemanticDiffRecord(BaseModel):
83
- id: int
84
- task_id: str
85
- before_snapshot_path: str
86
- after_snapshot_path: str
87
- classifications: list[dict]
88
- summary: str
89
- created_at: str
90
-
91
-
92
- class AgentHandoffRecord(BaseModel):
93
- id: str
94
- task_id: str
95
- from_agent: str
96
- to_agent: str
97
- run_id: str
98
- manifest_path: str
99
- status: str
100
- created_at: str
101
-
102
-
103
- class CorrectionManifestRecord(BaseModel):
104
- id: str
105
- task_id: str
106
- run_id: Optional[str] = None
107
- manifest_path: str
108
- retry_budget: int
109
- attempt: int
110
- status: str
111
- created_at: str
112
-
113
-
114
- class VerificationRunRecord(BaseModel):
115
- id: str
116
- task_id: str
117
- sandbox: str
118
- environment: dict
119
- commands: list[dict]
120
- status: str
121
- started_at: str
122
- finished_at: Optional[str] = None
123
-
124
-
125
- def _lease_from_model(model: TaskLeaseModel) -> TaskLeaseRecord:
126
- return TaskLeaseRecord.model_validate(model.model_dump())
127
-
128
-
129
- class TaskLeaseRepository:
130
- def __init__(self, session: Session):
131
- self.session = session
132
-
133
- def acquire(
134
- self,
135
- task_id: str,
136
- owner: str,
137
- *,
138
- agent: Optional[str] = None,
139
- client_id: Optional[str] = None,
140
- run_id: Optional[str] = None,
141
- branch: Optional[str] = None,
142
- ttl_seconds: Optional[int] = None,
143
- force: bool = False,
144
- ) -> TaskLeaseRecord:
145
- active = self.active_for_task(task_id)
146
- if active is not None and not force:
147
- raise ValueError(f"Active lease already exists for task {task_id}")
148
- if active is not None and force:
149
- existing = self.session.get(TaskLeaseModel, active.id)
150
- if existing is not None:
151
- existing.status = "stale"
152
- existing.released_at = _utc_now()
153
- self.session.add(existing)
154
- self.session.commit()
155
-
156
- lease_id = str(uuid.uuid4())
157
- token = secrets.token_urlsafe(32)
158
- expires_at = None
159
- if ttl_seconds is not None:
160
- expires_at = (datetime.now(timezone.utc) + timedelta(seconds=ttl_seconds)).isoformat()
161
-
162
- model = TaskLeaseModel(
163
- id=lease_id,
164
- task_id=task_id,
165
- owner=owner,
166
- agent=agent,
167
- client_id=client_id,
168
- run_id=run_id,
169
- branch=branch,
170
- lease_token=token,
171
- status="active",
172
- created_at=_utc_now(),
173
- expires_at=expires_at,
174
- )
175
- self.session.add(model)
176
- try:
177
- self.session.commit()
178
- except IntegrityError as exc:
179
- # Lost a concurrent race: another writer inserted the active lease between
180
- # our active_for_task() check and this commit. The partial unique index
181
- # rejected the duplicate — surface it as the same conflict callers expect.
182
- self.session.rollback()
183
- raise ValueError(f"Active lease already exists for task {task_id}") from exc
184
- self.session.refresh(model)
185
- return _lease_from_model(model)
186
-
187
- def release(self, task_id: str, lease_token: str, status: str = "released") -> bool:
188
- active = self.active_for_task(task_id)
189
- if active is None or active.lease_token != lease_token:
190
- return False
191
- model = self.session.get(TaskLeaseModel, active.id)
192
- if model is None:
193
- return False
194
- model.status = status
195
- model.released_at = _utc_now()
196
- self.session.add(model)
197
- self.session.commit()
198
- return True
199
-
200
- def active_for_task(self, task_id: str) -> TaskLeaseRecord | None:
201
- statement = (
202
- select(TaskLeaseModel)
203
- .where(TaskLeaseModel.task_id == task_id)
204
- .where(TaskLeaseModel.status == "active")
205
- )
206
- model = self.session.exec(statement).first()
207
- if model is None:
208
- return None
209
- if model.expires_at:
210
- expires = datetime.fromisoformat(model.expires_at)
211
- if expires.tzinfo is None:
212
- expires = expires.replace(tzinfo=timezone.utc)
213
- if datetime.now(timezone.utc) > expires:
214
- model.status = "stale"
215
- model.released_at = _utc_now()
216
- self.session.add(model)
217
- self.session.commit()
218
- return None
219
- return _lease_from_model(model)
220
-
221
- def validate(self, task_id: str, lease_token: str) -> bool:
222
- active = self.active_for_task(task_id)
223
- return active is not None and active.lease_token == lease_token
224
-
225
- def renew(self, task_id: str, lease_token: str, ttl_seconds: int) -> TaskLeaseRecord | None:
226
- """Push the lease's expiry out by ``ttl_seconds`` from now. Returns the updated
227
- record, or None when the token is invalid / the lease already expired."""
228
- active = self.active_for_task(task_id)
229
- if active is None or active.lease_token != lease_token:
230
- return None
231
- model = self.session.get(TaskLeaseModel, active.id)
232
- if model is None:
233
- return None
234
- model.expires_at = (datetime.now(timezone.utc) + timedelta(seconds=ttl_seconds)).isoformat()
235
- self.session.add(model)
236
- self.session.commit()
237
- self.session.refresh(model)
238
- return _lease_from_model(model)
239
-
240
- def list_leases(self, *, active_only: bool = True) -> list[tuple[TaskLeaseRecord, bool]]:
241
- """Return ``(lease, expired)`` pairs for fleet supervision. ``expired`` means the
242
- lease is still marked active but past its TTL (a crashed/disconnected agent)."""
243
- statement = select(TaskLeaseModel)
244
- if active_only:
245
- statement = statement.where(TaskLeaseModel.status == "active")
246
- statement = statement.order_by(col(TaskLeaseModel.created_at).desc())
247
- now = datetime.now(timezone.utc)
248
- results: list[tuple[TaskLeaseRecord, bool]] = []
249
- for model in self.session.exec(statement).all():
250
- expired = False
251
- if model.status == "active" and model.expires_at:
252
- expires = datetime.fromisoformat(model.expires_at)
253
- if expires.tzinfo is None:
254
- expires = expires.replace(tzinfo=timezone.utc)
255
- expired = now > expires
256
- results.append((_lease_from_model(model), expired))
257
- return results
258
-
259
-
260
- class ShellSessionRepository:
261
- def __init__(self, session: Session):
262
- self.session = session
263
-
264
- def start(
265
- self,
266
- task_id: str,
267
- shell: str,
268
- cwd: str,
269
- *,
270
- lease_id: Optional[str] = None,
271
- status: str = "active",
272
- ) -> ShellSessionRecord:
273
- session_id = str(uuid.uuid4())
274
- model = ShellSessionModel(
275
- id=session_id,
276
- task_id=task_id,
277
- lease_id=lease_id,
278
- shell=shell,
279
- cwd=cwd,
280
- status=status,
281
- started_at=_utc_now(),
282
- )
283
- self.session.add(model)
284
- self.session.commit()
285
- self.session.refresh(model)
286
- return ShellSessionRecord.model_validate(model.model_dump())
287
-
288
- def finish(self, session_id: str, status: str) -> bool:
289
- model = self.session.get(ShellSessionModel, session_id)
290
- if model is None:
291
- return False
292
- model.status = status
293
- model.ended_at = _utc_now()
294
- self.session.add(model)
295
- self.session.commit()
296
- return True
297
-
298
-
299
- class ShellCommandRepository:
300
- def __init__(self, session: Session):
301
- self.session = session
302
-
303
- def record(
304
- self,
305
- task_id: str,
306
- command: str,
307
- status: str,
308
- *,
309
- session_id: Optional[str] = None,
310
- exit_code: Optional[int] = None,
311
- reason: str = "",
312
- stdout_path: str = "",
313
- stderr_path: str = "",
314
- ) -> ShellCommandRecord:
315
- model = ShellCommandEventModel(
316
- task_id=task_id,
317
- session_id=session_id,
318
- command=command,
319
- status=status,
320
- exit_code=exit_code,
321
- reason=reason,
322
- stdout_path=stdout_path,
323
- stderr_path=stderr_path,
324
- created_at=_utc_now(),
325
- )
326
- self.session.add(model)
327
- self.session.commit()
328
- self.session.refresh(model)
329
- return ShellCommandRecord.model_validate(model.model_dump())
330
-
331
-
332
- class FileChangeRepository:
333
- def __init__(self, session: Session):
334
- self.session = session
335
-
336
- def record(
337
- self,
338
- path: str,
339
- operation: str,
340
- allowed: bool,
341
- *,
342
- task_id: Optional[str] = None,
343
- lease_id: Optional[str] = None,
344
- session_id: Optional[str] = None,
345
- reason: str = "",
346
- ) -> FileChangeRecord:
347
- model = FileChangeEventModel(
348
- task_id=task_id,
349
- lease_id=lease_id,
350
- session_id=session_id,
351
- path=path,
352
- operation=operation,
353
- allowed=allowed,
354
- reason=reason,
355
- created_at=_utc_now(),
356
- )
357
- self.session.add(model)
358
- self.session.commit()
359
- self.session.refresh(model)
360
- return FileChangeRecord.model_validate(model.model_dump())
361
-
362
- def list_for_task(self, task_id: str) -> list[FileChangeRecord]:
363
- statement = (
364
- select(FileChangeEventModel)
365
- .where(FileChangeEventModel.task_id == task_id)
366
- .order_by(col(FileChangeEventModel.created_at))
367
- )
368
- return [FileChangeRecord.model_validate(m.model_dump()) for m in self.session.exec(statement).all()]
369
-
370
-
371
- class SemanticDiffRepository:
372
- def __init__(self, session: Session):
373
- self.session = session
374
-
375
- def save(
376
- self,
377
- task_id: str,
378
- before_snapshot_path: str,
379
- after_snapshot_path: str,
380
- classifications: list[dict],
381
- summary: str,
382
- ) -> SemanticDiffRecord:
383
- model = SemanticDiffModel(
384
- task_id=task_id,
385
- before_snapshot_path=before_snapshot_path,
386
- after_snapshot_path=after_snapshot_path,
387
- classifications_json=json.dumps(classifications),
388
- summary=summary,
389
- created_at=_utc_now(),
390
- )
391
- self.session.add(model)
392
- self.session.commit()
393
- self.session.refresh(model)
394
- return SemanticDiffRecord(
395
- id=model.id or 0,
396
- task_id=model.task_id,
397
- before_snapshot_path=model.before_snapshot_path,
398
- after_snapshot_path=model.after_snapshot_path,
399
- classifications=classifications,
400
- summary=model.summary,
401
- created_at=model.created_at,
402
- )
403
-
404
- def latest_for_task(self, task_id: str) -> SemanticDiffRecord | None:
405
- statement = (
406
- select(SemanticDiffModel)
407
- .where(SemanticDiffModel.task_id == task_id)
408
- .order_by(col(SemanticDiffModel.created_at).desc())
409
- )
410
- model = self.session.exec(statement).first()
411
- if model is None:
412
- return None
413
- return SemanticDiffRecord(
414
- id=model.id or 0,
415
- task_id=model.task_id,
416
- before_snapshot_path=model.before_snapshot_path,
417
- after_snapshot_path=model.after_snapshot_path,
418
- classifications=json.loads(model.classifications_json),
419
- summary=model.summary,
420
- created_at=model.created_at,
421
- )
422
-
423
-
424
- class AgentHandoffRepository:
425
- def __init__(self, session: Session):
426
- self.session = session
427
-
428
- def save(
429
- self,
430
- task_id: str,
431
- from_agent: str,
432
- to_agent: str,
433
- run_id: str,
434
- manifest_path: str,
435
- status: str,
436
- *,
437
- handoff_id: Optional[str] = None,
438
- ) -> AgentHandoffRecord:
439
- hid = handoff_id or str(uuid.uuid4())
440
- model = AgentHandoffModel(
441
- id=hid,
442
- task_id=task_id,
443
- from_agent=from_agent,
444
- to_agent=to_agent,
445
- run_id=run_id,
446
- manifest_path=manifest_path,
447
- status=status,
448
- created_at=_utc_now(),
449
- )
450
- self.session.add(model)
451
- self.session.commit()
452
- self.session.refresh(model)
453
- return AgentHandoffRecord.model_validate(model.model_dump())
454
-
455
-
456
- class CorrectionManifestRepository:
457
- def __init__(self, session: Session):
458
- self.session = session
459
-
460
- def save(
461
- self,
462
- task_id: str,
463
- manifest_path: str,
464
- status: str,
465
- *,
466
- run_id: Optional[str] = None,
467
- retry_budget: int = 3,
468
- attempt: int = 0,
469
- manifest_id: Optional[str] = None,
470
- ) -> CorrectionManifestRecord:
471
- mid = manifest_id or str(uuid.uuid4())
472
- model = CorrectionManifestModel(
473
- id=mid,
474
- task_id=task_id,
475
- run_id=run_id,
476
- manifest_path=manifest_path,
477
- retry_budget=retry_budget,
478
- attempt=attempt,
479
- status=status,
480
- created_at=_utc_now(),
481
- )
482
- self.session.add(model)
483
- self.session.commit()
484
- self.session.refresh(model)
485
- return CorrectionManifestRecord.model_validate(model.model_dump())
486
-
487
- def latest_for_task(self, task_id: str) -> CorrectionManifestRecord | None:
488
- statement = (
489
- select(CorrectionManifestModel)
490
- .where(CorrectionManifestModel.task_id == task_id)
491
- .order_by(col(CorrectionManifestModel.created_at).desc())
492
- )
493
- model = self.session.exec(statement).first()
494
- if model is None:
495
- return None
496
- return CorrectionManifestRecord.model_validate(model.model_dump())
497
-
498
-
499
- class VerificationRunRepository:
500
- def __init__(self, session: Session):
501
- self.session = session
502
-
503
- def save(
504
- self,
505
- task_id: str,
506
- sandbox: str,
507
- environment: dict,
508
- commands: list[dict],
509
- status: str,
510
- *,
511
- run_id: Optional[str] = None,
512
- finished_at: Optional[str] = None,
513
- ) -> VerificationRunRecord:
514
- rid = run_id or str(uuid.uuid4())
515
- model = VerificationRunModel(
516
- id=rid,
517
- task_id=task_id,
518
- sandbox=sandbox,
519
- environment_json=json.dumps(environment),
520
- commands_json=json.dumps(commands),
521
- status=status,
522
- started_at=_utc_now(),
523
- finished_at=finished_at,
524
- )
525
- self.session.add(model)
526
- self.session.commit()
527
- self.session.refresh(model)
528
- return VerificationRunRecord(
529
- id=model.id,
530
- task_id=model.task_id,
531
- sandbox=model.sandbox,
532
- environment=environment,
533
- commands=commands,
534
- status=model.status,
535
- started_at=model.started_at,
536
- finished_at=model.finished_at,
537
- )
538
-
539
- def list_for_task(self, task_id: str) -> list[VerificationRunRecord]:
540
- statement = (
541
- select(VerificationRunModel)
542
- .where(VerificationRunModel.task_id == task_id)
543
- .order_by(col(VerificationRunModel.started_at))
544
- )
545
- records: list[VerificationRunRecord] = []
546
- for model in self.session.exec(statement).all():
547
- records.append(VerificationRunRecord(
548
- id=model.id,
549
- task_id=model.task_id,
550
- sandbox=model.sandbox,
551
- environment=json.loads(model.environment_json),
552
- commands=json.loads(model.commands_json),
553
- status=model.status,
554
- started_at=model.started_at,
555
- finished_at=model.finished_at,
556
- ))
557
- return records