draft-board 0.1.0-beta.0

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 (250) hide show
  1. package/app/backend/.env.example +9 -0
  2. package/app/backend/.smartkanban/evidence/8b383839-cbec-45af-86ee-c7708d075cbe/bddf2ed5-2e21-4d46-a62b-10b87f1642a6_patch.txt +195 -0
  3. package/app/backend/.smartkanban/evidence/8b383839-cbec-45af-86ee-c7708d075cbe/bddf2ed5-2e21-4d46-a62b-10b87f1642a6_stat.txt +6 -0
  4. package/app/backend/CURL_EXAMPLES.md +335 -0
  5. package/app/backend/ENV_SETUP.md +65 -0
  6. package/app/backend/alembic/env.py +71 -0
  7. package/app/backend/alembic/script.py.mako +28 -0
  8. package/app/backend/alembic/versions/001_initial_schema.py +104 -0
  9. package/app/backend/alembic/versions/002_add_jobs_table.py +52 -0
  10. package/app/backend/alembic/versions/003_add_workspace_table.py +48 -0
  11. package/app/backend/alembic/versions/004_add_evidence_table.py +56 -0
  12. package/app/backend/alembic/versions/005_add_verification_commands.py +32 -0
  13. package/app/backend/alembic/versions/006_add_planner_lock_table.py +39 -0
  14. package/app/backend/alembic/versions/007_add_revision_review_tables.py +126 -0
  15. package/app/backend/alembic/versions/008_add_revision_idempotency_and_traceability.py +52 -0
  16. package/app/backend/alembic/versions/009_add_job_health_fields.py +46 -0
  17. package/app/backend/alembic/versions/010_add_review_comment_line_content.py +36 -0
  18. package/app/backend/alembic/versions/011_add_analysis_cache.py +47 -0
  19. package/app/backend/alembic/versions/012_add_boards_table.py +102 -0
  20. package/app/backend/alembic/versions/013_add_ticket_blocking.py +45 -0
  21. package/app/backend/alembic/versions/014_add_agent_sessions.py +220 -0
  22. package/app/backend/alembic/versions/015_add_ticket_sort_order.py +33 -0
  23. package/app/backend/alembic/versions/03220f0b93ae_add_pr_fields_to_ticket.py +49 -0
  24. package/app/backend/alembic/versions/0c2d89fff3b1_seed_board_configs_from_yaml.py +206 -0
  25. package/app/backend/alembic/versions/3348e5cf54c1_add_merge_checklist_table.py +67 -0
  26. package/app/backend/alembic/versions/357c780ee445_add_goal_status.py +34 -0
  27. package/app/backend/alembic/versions/553340b7e26c_add_autonomy_fields_to_goal.py +65 -0
  28. package/app/backend/alembic/versions/774dc335c679_merge_migration_heads.py +23 -0
  29. package/app/backend/alembic/versions/7b307e847cbd_merge_heads.py +23 -0
  30. package/app/backend/alembic/versions/82ecd978cc70_add_missing_indexes.py +48 -0
  31. package/app/backend/alembic/versions/8ef5054dc280_add_normalized_log_entries.py +173 -0
  32. package/app/backend/alembic/versions/8f3e2bd8ea3b_merge_migration_heads.py +23 -0
  33. package/app/backend/alembic/versions/9d17f0698d3b_add_config_column_to_boards_table.py +30 -0
  34. package/app/backend/alembic/versions/add_agent_conversation_history.py +72 -0
  35. package/app/backend/alembic/versions/add_job_variant.py +34 -0
  36. package/app/backend/alembic/versions/add_performance_indexes.py +95 -0
  37. package/app/backend/alembic/versions/add_repos_and_board_repos.py +174 -0
  38. package/app/backend/alembic/versions/add_session_id_to_jobs.py +27 -0
  39. package/app/backend/alembic/versions/add_sqlite_backend_tables.py +104 -0
  40. package/app/backend/alembic/versions/b10fb0b62240_add_diff_content_to_revisions.py +34 -0
  41. package/app/backend/alembic.ini +89 -0
  42. package/app/backend/app/__init__.py +3 -0
  43. package/app/backend/app/data_dir.py +85 -0
  44. package/app/backend/app/database.py +70 -0
  45. package/app/backend/app/database_sync.py +64 -0
  46. package/app/backend/app/dependencies/__init__.py +5 -0
  47. package/app/backend/app/dependencies/auth.py +80 -0
  48. package/app/backend/app/dependencies.py +43 -0
  49. package/app/backend/app/exceptions.py +178 -0
  50. package/app/backend/app/executors/__init__.py +1 -0
  51. package/app/backend/app/executors/adapters/__init__.py +1 -0
  52. package/app/backend/app/executors/adapters/aider.py +152 -0
  53. package/app/backend/app/executors/adapters/amazon_q.py +103 -0
  54. package/app/backend/app/executors/adapters/amp.py +123 -0
  55. package/app/backend/app/executors/adapters/claude.py +177 -0
  56. package/app/backend/app/executors/adapters/cline.py +127 -0
  57. package/app/backend/app/executors/adapters/codex.py +167 -0
  58. package/app/backend/app/executors/adapters/copilot.py +202 -0
  59. package/app/backend/app/executors/adapters/cursor.py +87 -0
  60. package/app/backend/app/executors/adapters/droid.py +123 -0
  61. package/app/backend/app/executors/adapters/gemini.py +132 -0
  62. package/app/backend/app/executors/adapters/goose.py +131 -0
  63. package/app/backend/app/executors/adapters/opencode.py +123 -0
  64. package/app/backend/app/executors/adapters/qwen.py +123 -0
  65. package/app/backend/app/executors/plugins/__init__.py +1 -0
  66. package/app/backend/app/executors/registry.py +202 -0
  67. package/app/backend/app/executors/spec.py +226 -0
  68. package/app/backend/app/main.py +486 -0
  69. package/app/backend/app/middleware/__init__.py +13 -0
  70. package/app/backend/app/middleware/idempotency.py +426 -0
  71. package/app/backend/app/middleware/rate_limit.py +312 -0
  72. package/app/backend/app/middleware/security_headers.py +43 -0
  73. package/app/backend/app/middleware/timeout.py +37 -0
  74. package/app/backend/app/models/__init__.py +56 -0
  75. package/app/backend/app/models/agent_conversation_history.py +56 -0
  76. package/app/backend/app/models/agent_session.py +127 -0
  77. package/app/backend/app/models/analysis_cache.py +49 -0
  78. package/app/backend/app/models/base.py +9 -0
  79. package/app/backend/app/models/board.py +79 -0
  80. package/app/backend/app/models/board_repo.py +68 -0
  81. package/app/backend/app/models/cost_budget.py +42 -0
  82. package/app/backend/app/models/enums.py +40 -0
  83. package/app/backend/app/models/evidence.py +132 -0
  84. package/app/backend/app/models/goal.py +102 -0
  85. package/app/backend/app/models/idempotency_entry.py +30 -0
  86. package/app/backend/app/models/job.py +163 -0
  87. package/app/backend/app/models/job_queue.py +39 -0
  88. package/app/backend/app/models/kv_store.py +28 -0
  89. package/app/backend/app/models/merge_checklist.py +87 -0
  90. package/app/backend/app/models/normalized_log.py +100 -0
  91. package/app/backend/app/models/planner_lock.py +43 -0
  92. package/app/backend/app/models/rate_limit_entry.py +25 -0
  93. package/app/backend/app/models/repo.py +66 -0
  94. package/app/backend/app/models/review_comment.py +91 -0
  95. package/app/backend/app/models/review_summary.py +69 -0
  96. package/app/backend/app/models/revision.py +130 -0
  97. package/app/backend/app/models/ticket.py +223 -0
  98. package/app/backend/app/models/ticket_event.py +83 -0
  99. package/app/backend/app/models/user.py +47 -0
  100. package/app/backend/app/models/workspace.py +71 -0
  101. package/app/backend/app/redis_client.py +119 -0
  102. package/app/backend/app/routers/__init__.py +29 -0
  103. package/app/backend/app/routers/agents.py +296 -0
  104. package/app/backend/app/routers/auth.py +94 -0
  105. package/app/backend/app/routers/board.py +885 -0
  106. package/app/backend/app/routers/dashboard.py +351 -0
  107. package/app/backend/app/routers/debug.py +528 -0
  108. package/app/backend/app/routers/evidence.py +96 -0
  109. package/app/backend/app/routers/executors.py +324 -0
  110. package/app/backend/app/routers/goals.py +574 -0
  111. package/app/backend/app/routers/jobs.py +448 -0
  112. package/app/backend/app/routers/maintenance.py +172 -0
  113. package/app/backend/app/routers/merge.py +360 -0
  114. package/app/backend/app/routers/planner.py +537 -0
  115. package/app/backend/app/routers/pull_requests.py +382 -0
  116. package/app/backend/app/routers/repos.py +263 -0
  117. package/app/backend/app/routers/revisions.py +939 -0
  118. package/app/backend/app/routers/settings.py +267 -0
  119. package/app/backend/app/routers/tickets.py +2003 -0
  120. package/app/backend/app/routers/webhooks.py +143 -0
  121. package/app/backend/app/routers/websocket.py +249 -0
  122. package/app/backend/app/schemas/__init__.py +109 -0
  123. package/app/backend/app/schemas/board.py +87 -0
  124. package/app/backend/app/schemas/common.py +33 -0
  125. package/app/backend/app/schemas/evidence.py +87 -0
  126. package/app/backend/app/schemas/goal.py +90 -0
  127. package/app/backend/app/schemas/job.py +97 -0
  128. package/app/backend/app/schemas/merge.py +139 -0
  129. package/app/backend/app/schemas/planner.py +500 -0
  130. package/app/backend/app/schemas/repo.py +187 -0
  131. package/app/backend/app/schemas/review.py +137 -0
  132. package/app/backend/app/schemas/revision.py +114 -0
  133. package/app/backend/app/schemas/ticket.py +238 -0
  134. package/app/backend/app/schemas/ticket_event.py +72 -0
  135. package/app/backend/app/schemas/workspace.py +19 -0
  136. package/app/backend/app/services/__init__.py +31 -0
  137. package/app/backend/app/services/agent_memory_service.py +223 -0
  138. package/app/backend/app/services/agent_registry.py +346 -0
  139. package/app/backend/app/services/agent_session_manager.py +318 -0
  140. package/app/backend/app/services/agent_session_service.py +219 -0
  141. package/app/backend/app/services/agent_tools.py +379 -0
  142. package/app/backend/app/services/auth_service.py +98 -0
  143. package/app/backend/app/services/autonomy_service.py +380 -0
  144. package/app/backend/app/services/board_repo_service.py +201 -0
  145. package/app/backend/app/services/board_service.py +326 -0
  146. package/app/backend/app/services/cleanup_service.py +1085 -0
  147. package/app/backend/app/services/config_service.py +908 -0
  148. package/app/backend/app/services/context_gatherer.py +557 -0
  149. package/app/backend/app/services/cost_tracking_service.py +293 -0
  150. package/app/backend/app/services/cursor_log_normalizer.py +536 -0
  151. package/app/backend/app/services/delivery_pipeline.py +440 -0
  152. package/app/backend/app/services/executor_service.py +634 -0
  153. package/app/backend/app/services/git_host/__init__.py +11 -0
  154. package/app/backend/app/services/git_host/factory.py +87 -0
  155. package/app/backend/app/services/git_host/github.py +270 -0
  156. package/app/backend/app/services/git_host/gitlab.py +194 -0
  157. package/app/backend/app/services/git_host/protocol.py +75 -0
  158. package/app/backend/app/services/git_merge_simple.py +346 -0
  159. package/app/backend/app/services/git_ops.py +384 -0
  160. package/app/backend/app/services/github_service.py +233 -0
  161. package/app/backend/app/services/goal_service.py +113 -0
  162. package/app/backend/app/services/job_service.py +423 -0
  163. package/app/backend/app/services/job_watchdog_service.py +424 -0
  164. package/app/backend/app/services/langchain_adapter.py +122 -0
  165. package/app/backend/app/services/llm_provider_clients.py +351 -0
  166. package/app/backend/app/services/llm_service.py +285 -0
  167. package/app/backend/app/services/log_normalizer.py +342 -0
  168. package/app/backend/app/services/log_stream_service.py +276 -0
  169. package/app/backend/app/services/merge_checklist_service.py +264 -0
  170. package/app/backend/app/services/merge_service.py +784 -0
  171. package/app/backend/app/services/orchestrator_log.py +84 -0
  172. package/app/backend/app/services/planner_service.py +1662 -0
  173. package/app/backend/app/services/planner_tick_sync.py +1040 -0
  174. package/app/backend/app/services/queued_message_service.py +156 -0
  175. package/app/backend/app/services/reliability_wrapper.py +389 -0
  176. package/app/backend/app/services/repo_discovery_service.py +318 -0
  177. package/app/backend/app/services/review_service.py +334 -0
  178. package/app/backend/app/services/revision_service.py +389 -0
  179. package/app/backend/app/services/safe_autopilot.py +510 -0
  180. package/app/backend/app/services/sqlite_worker.py +372 -0
  181. package/app/backend/app/services/task_dispatch.py +135 -0
  182. package/app/backend/app/services/ticket_generation_service.py +1781 -0
  183. package/app/backend/app/services/ticket_service.py +486 -0
  184. package/app/backend/app/services/udar_planner_service.py +1007 -0
  185. package/app/backend/app/services/webhook_service.py +126 -0
  186. package/app/backend/app/services/workspace_service.py +465 -0
  187. package/app/backend/app/services/worktree_file_service.py +92 -0
  188. package/app/backend/app/services/worktree_validator.py +213 -0
  189. package/app/backend/app/sqlite_kv.py +278 -0
  190. package/app/backend/app/state_machine.py +128 -0
  191. package/app/backend/app/templates/__init__.py +5 -0
  192. package/app/backend/app/templates/registry.py +243 -0
  193. package/app/backend/app/utils/__init__.py +5 -0
  194. package/app/backend/app/utils/artifact_reader.py +87 -0
  195. package/app/backend/app/utils/circuit_breaker.py +229 -0
  196. package/app/backend/app/utils/db_retry.py +136 -0
  197. package/app/backend/app/utils/ignored_fields.py +123 -0
  198. package/app/backend/app/utils/validators.py +54 -0
  199. package/app/backend/app/websocket/__init__.py +5 -0
  200. package/app/backend/app/websocket/manager.py +179 -0
  201. package/app/backend/app/websocket/state_tracker.py +113 -0
  202. package/app/backend/app/worker.py +3190 -0
  203. package/app/backend/calculator_tickets.json +40 -0
  204. package/app/backend/canary_tests.sh +591 -0
  205. package/app/backend/celerybeat-schedule +0 -0
  206. package/app/backend/celerybeat-schedule-shm +0 -0
  207. package/app/backend/celerybeat-schedule-wal +0 -0
  208. package/app/backend/logs/.gitkeep +3 -0
  209. package/app/backend/multiplication_division_implementation_tickets.json +55 -0
  210. package/app/backend/multiplication_division_tickets.json +42 -0
  211. package/app/backend/pyproject.toml +45 -0
  212. package/app/backend/requirements-dev.txt +8 -0
  213. package/app/backend/requirements.txt +20 -0
  214. package/app/backend/run.sh +30 -0
  215. package/app/backend/run_with_logs.sh +10 -0
  216. package/app/backend/scientific_calculator_tickets.json +40 -0
  217. package/app/backend/scripts/extract_openapi.py +21 -0
  218. package/app/backend/scripts/seed_demo.py +187 -0
  219. package/app/backend/setup_demo_review.py +302 -0
  220. package/app/backend/test_actual_parse.py +41 -0
  221. package/app/backend/test_agent_streaming.py +61 -0
  222. package/app/backend/test_parse.py +51 -0
  223. package/app/backend/test_streaming.py +51 -0
  224. package/app/backend/test_subprocess_streaming.py +50 -0
  225. package/app/backend/tests/__init__.py +1 -0
  226. package/app/backend/tests/conftest.py +46 -0
  227. package/app/backend/tests/test_auth.py +341 -0
  228. package/app/backend/tests/test_autonomy_service.py +391 -0
  229. package/app/backend/tests/test_cleanup_service_safety.py +417 -0
  230. package/app/backend/tests/test_middleware.py +279 -0
  231. package/app/backend/tests/test_planner_providers.py +290 -0
  232. package/app/backend/tests/test_planner_unblock.py +183 -0
  233. package/app/backend/tests/test_revision_invariants.py +618 -0
  234. package/app/backend/tests/test_sqlite_kv.py +290 -0
  235. package/app/backend/tests/test_sqlite_worker.py +353 -0
  236. package/app/backend/tests/test_task_dispatch.py +100 -0
  237. package/app/backend/tests/test_ticket_validation.py +304 -0
  238. package/app/backend/tests/test_udar_agent.py +693 -0
  239. package/app/backend/tests/test_webhook_service.py +184 -0
  240. package/app/backend/tickets_output.json +59 -0
  241. package/app/backend/user_management_tickets.json +50 -0
  242. package/app/backend/uvicorn.log +0 -0
  243. package/app/draft.yaml +313 -0
  244. package/app/frontend/dist/assets/index-LcjCczu5.js +155 -0
  245. package/app/frontend/dist/assets/index-_FP_279e.css +1 -0
  246. package/app/frontend/dist/index.html +14 -0
  247. package/app/frontend/dist/vite.svg +1 -0
  248. package/app/frontend/package.json +101 -0
  249. package/bin/cli.js +527 -0
  250. package/package.json +37 -0
@@ -0,0 +1,137 @@
1
+ """Pydantic schemas for Review entities (comments and summaries)."""
2
+
3
+ from datetime import datetime
4
+ from enum import StrEnum
5
+
6
+ from pydantic import BaseModel, Field
7
+
8
+
9
+ class AuthorType(StrEnum):
10
+ """Enum representing the type of author for a review comment."""
11
+
12
+ HUMAN = "human"
13
+ AGENT = "agent"
14
+ SYSTEM = "system"
15
+
16
+
17
+ class ReviewDecision(StrEnum):
18
+ """Enum representing the review decision."""
19
+
20
+ APPROVED = "approved"
21
+ CHANGES_REQUESTED = "changes_requested"
22
+
23
+
24
+ # Review Comment Schemas
25
+
26
+
27
+ class ReviewCommentCreate(BaseModel):
28
+ """Schema for creating a review comment."""
29
+
30
+ file_path: str = Field(..., min_length=1, max_length=500)
31
+ line_number: int = Field(..., ge=1)
32
+ body: str = Field(..., min_length=1)
33
+ author_type: AuthorType = Field(default=AuthorType.HUMAN)
34
+ # Optional: client can provide hunk_header for anchor calculation
35
+ hunk_header: str | None = Field(
36
+ None,
37
+ description="Diff hunk header (e.g., '@@ -10,5 +10,7 @@') for anchor stability",
38
+ )
39
+ line_content: str | None = Field(
40
+ None, description="Content of the line being commented on for anchor stability"
41
+ )
42
+
43
+
44
+ class ReviewCommentResponse(BaseModel):
45
+ """Schema for review comment response."""
46
+
47
+ id: str
48
+ revision_id: str
49
+ file_path: str
50
+ line_number: int
51
+ anchor: str
52
+ body: str
53
+ author_type: AuthorType
54
+ resolved: bool
55
+ created_at: datetime
56
+ line_content: str | None = None
57
+
58
+ model_config = {"from_attributes": True}
59
+
60
+
61
+ class ReviewCommentListResponse(BaseModel):
62
+ """Schema for list of review comments."""
63
+
64
+ comments: list[ReviewCommentResponse]
65
+ total: int
66
+ unresolved_count: int
67
+
68
+
69
+ # Review Summary Schemas
70
+
71
+
72
+ class ReviewSubmit(BaseModel):
73
+ """Schema for submitting a review decision."""
74
+
75
+ decision: ReviewDecision
76
+ summary: str = Field(
77
+ default="", description="High-level review feedback (optional)"
78
+ )
79
+ auto_run_fix: bool = Field(
80
+ default=True,
81
+ description="If changes_requested, automatically trigger new agent execution",
82
+ )
83
+ create_pr: bool = Field(
84
+ default=False,
85
+ description="If approved, create a GitHub PR instead of merging directly to main",
86
+ )
87
+
88
+
89
+ class ReviewSummaryResponse(BaseModel):
90
+ """Schema for review summary response."""
91
+
92
+ id: str
93
+ revision_id: str
94
+ decision: ReviewDecision
95
+ body: str
96
+ created_at: datetime
97
+
98
+ model_config = {"from_attributes": True}
99
+
100
+
101
+ # Feedback Bundle Schema
102
+
103
+
104
+ class FeedbackComment(BaseModel):
105
+ """Schema for a comment in the feedback bundle."""
106
+
107
+ file_path: str
108
+ line_number: int
109
+ anchor: str
110
+ body: str
111
+ line_content: str | None = Field(
112
+ default=None,
113
+ description="Content of the line being commented on, helps agent locate the issue",
114
+ )
115
+ orphaned: bool = Field(
116
+ default=False,
117
+ description="True if this comment's anchor cannot be found in the current diff",
118
+ )
119
+
120
+
121
+ class FeedbackBundle(BaseModel):
122
+ """Schema for the feedback bundle sent to the agent.
123
+
124
+ This is the structured feedback that gets injected into the agent prompt
125
+ when creating a new revision after changes are requested.
126
+ """
127
+
128
+ ticket_id: str
129
+ revision_id: str
130
+ revision_number: int
131
+ decision: str
132
+ summary: str
133
+ comments: list[FeedbackComment]
134
+ orphaned_comment_count: int = Field(
135
+ default=0,
136
+ description="Number of comments that could not be anchored to the current diff",
137
+ )
@@ -0,0 +1,114 @@
1
+ """Pydantic schemas for Revision entity."""
2
+
3
+ from datetime import datetime
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+ # Import RevisionStatus from models to avoid duplication
8
+ from app.models.revision import RevisionStatus
9
+
10
+
11
+ class RevisionResponse(BaseModel):
12
+ """Schema for revision response."""
13
+
14
+ id: str
15
+ ticket_id: str
16
+ job_id: str
17
+ number: int
18
+ status: RevisionStatus
19
+ diff_stat_evidence_id: str | None
20
+ diff_patch_evidence_id: str | None
21
+ created_at: datetime
22
+ unresolved_comment_count: int = Field(
23
+ default=0, description="Number of unresolved review comments"
24
+ )
25
+
26
+ model_config = {"from_attributes": True}
27
+
28
+
29
+ class RevisionDetailResponse(RevisionResponse):
30
+ """Schema for detailed revision response with diff content."""
31
+
32
+ diff_stat: str | None = Field(None, description="Git diff stat output")
33
+ diff_patch: str | None = Field(None, description="Full git diff patch")
34
+
35
+
36
+ class RevisionListResponse(BaseModel):
37
+ """Schema for list of revisions."""
38
+
39
+ revisions: list[RevisionResponse]
40
+ total: int
41
+
42
+
43
+ class RevisionDiffResponse(BaseModel):
44
+ """Schema for revision diff content (both stat and patch - heavyweight)."""
45
+
46
+ revision_id: str
47
+ diff_stat: str | None = Field(None, description="Git diff stat output")
48
+ diff_patch: str | None = Field(None, description="Full git diff patch")
49
+ files: list["DiffFile"] = Field(
50
+ default_factory=list, description="Parsed diff files"
51
+ )
52
+
53
+
54
+ class DiffSummaryResponse(BaseModel):
55
+ """Schema for lightweight diff summary (stat + file list only).
56
+
57
+ Use this endpoint for initial load - no heavy patch content.
58
+ """
59
+
60
+ revision_id: str
61
+ diff_stat: str | None = Field(None, description="Git diff stat output")
62
+ files: list["DiffFile"] = Field(
63
+ default_factory=list, description="Parsed diff files"
64
+ )
65
+
66
+
67
+ class DiffPatchResponse(BaseModel):
68
+ """Schema for heavyweight diff patch content.
69
+
70
+ Only fetch this when user actually opens the diff viewer.
71
+ """
72
+
73
+ revision_id: str
74
+ diff_patch: str | None = Field(None, description="Full git diff patch")
75
+
76
+
77
+ class DiffFile(BaseModel):
78
+ """Schema for a single file in the diff."""
79
+
80
+ path: str
81
+ old_path: str | None = None
82
+ additions: int = 0
83
+ deletions: int = 0
84
+ status: str = Field(
85
+ default="modified",
86
+ description="File status: added, deleted, modified, renamed",
87
+ )
88
+
89
+
90
+ class TimelineEvent(BaseModel):
91
+ """Schema for a single event in the revision timeline."""
92
+
93
+ id: str
94
+ event_type: str = Field(
95
+ description="Event type: revision_created, comment_added, review_submitted, job_queued, job_completed"
96
+ )
97
+ actor: str = Field(description="Who triggered this event (human, agent, system)")
98
+ message: str = Field(description="Human-readable description of the event")
99
+ created_at: datetime
100
+ metadata: dict | None = Field(
101
+ default=None, description="Additional event-specific data"
102
+ )
103
+
104
+
105
+ class RevisionTimelineResponse(BaseModel):
106
+ """Schema for revision timeline (audit trail of events)."""
107
+
108
+ revision_id: str
109
+ events: list[TimelineEvent]
110
+
111
+
112
+ # For forward reference
113
+ RevisionDiffResponse.model_rebuild()
114
+ DiffSummaryResponse.model_rebuild()
@@ -0,0 +1,238 @@
1
+ """Pydantic schemas for Ticket entity."""
2
+
3
+ from datetime import datetime
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+ from app.state_machine import ActorType, TicketState
8
+
9
+
10
+ class TicketCreate(BaseModel):
11
+ """Schema for creating a new ticket."""
12
+
13
+ goal_id: str = Field(..., description="UUID of the parent goal")
14
+ title: str = Field(..., min_length=1, max_length=255)
15
+ description: str | None = None
16
+ priority: int | None = Field(None, ge=0, le=100)
17
+ blocked_by_ticket_id: str | None = Field(
18
+ None,
19
+ description="UUID of a ticket that blocks this one. This ticket cannot be executed until the blocker is DONE.",
20
+ )
21
+ actor_type: ActorType = Field(
22
+ default=ActorType.HUMAN,
23
+ description="Type of actor creating the ticket",
24
+ )
25
+ actor_id: str | None = Field(
26
+ None, description="ID of the actor creating the ticket"
27
+ )
28
+
29
+
30
+ class TicketUpdate(BaseModel):
31
+ """Schema for updating an existing ticket."""
32
+
33
+ title: str | None = Field(None, min_length=1, max_length=255)
34
+ description: str | None = None
35
+ priority: int | None = Field(None, ge=0, le=100)
36
+
37
+
38
+ class TicketResponse(BaseModel):
39
+ """Schema for ticket response."""
40
+
41
+ id: str
42
+ goal_id: str
43
+ title: str
44
+ description: str | None
45
+ state: TicketState
46
+ priority: int | None
47
+ sort_order: int | None = Field(
48
+ None, description="Manual sort order within state column"
49
+ )
50
+ blocked_by_ticket_id: str | None = Field(
51
+ None, description="UUID of ticket blocking this one"
52
+ )
53
+ blocked_by_ticket_title: str | None = Field(
54
+ None, description="Title of the ticket blocking this one (if loaded)"
55
+ )
56
+ goal_title: str | None = Field(
57
+ None, description="Title of the parent goal (if loaded)"
58
+ )
59
+ created_at: datetime
60
+ updated_at: datetime
61
+
62
+ model_config = {"from_attributes": True}
63
+
64
+
65
+ class TicketDetailResponse(TicketResponse):
66
+ """Schema for detailed ticket response with additional context."""
67
+
68
+ goal_title: str | None = None
69
+ goal_description: str | None = None
70
+ priority_label: str | None = None
71
+ state_display: str
72
+ blocked_by_ticket_title: str | None = Field(
73
+ None, description="Title of the ticket blocking this one"
74
+ )
75
+ is_blocked: bool = Field(
76
+ False, description="True if this ticket is blocked by an incomplete dependency"
77
+ )
78
+
79
+ @staticmethod
80
+ def get_priority_label(priority: int | None) -> str | None:
81
+ """Convert numeric priority to human-readable label."""
82
+ if priority is None:
83
+ return None
84
+ if priority >= 80:
85
+ return "High"
86
+ elif priority >= 50:
87
+ return "Medium"
88
+ elif priority >= 20:
89
+ return "Low"
90
+ else:
91
+ return "Very Low"
92
+
93
+ @staticmethod
94
+ def get_state_display(state: TicketState) -> str:
95
+ """Convert state enum to human-readable display text."""
96
+ display_map = {
97
+ TicketState.PROPOSED: "Proposed",
98
+ TicketState.PLANNED: "Planned",
99
+ TicketState.EXECUTING: "Executing",
100
+ TicketState.VERIFYING: "Verifying",
101
+ TicketState.NEEDS_HUMAN: "Needs Human",
102
+ TicketState.BLOCKED: "Blocked",
103
+ TicketState.DONE: "Done",
104
+ TicketState.ABANDONED: "Abandoned",
105
+ }
106
+ return display_map.get(state, state.value.title())
107
+
108
+
109
+ class TicketTransition(BaseModel):
110
+ """Schema for requesting a ticket state transition."""
111
+
112
+ to_state: TicketState = Field(..., description="Target state for the transition")
113
+ actor_type: ActorType = Field(
114
+ ..., description="Type of actor performing the transition"
115
+ )
116
+ actor_id: str | None = Field(
117
+ None, description="ID of the actor performing the transition"
118
+ )
119
+ reason: str | None = Field(None, description="Reason for the state transition")
120
+
121
+
122
+ class TicketWithGoal(TicketResponse):
123
+ """Schema for ticket response including goal information."""
124
+
125
+ goal_title: str | None = None
126
+
127
+
128
+ class TicketsByState(BaseModel):
129
+ """Schema for tickets grouped by state."""
130
+
131
+ state: TicketState
132
+ tickets: list[TicketResponse]
133
+
134
+
135
+ class BoardResponse(BaseModel):
136
+ """Schema for the board view - tickets grouped by state."""
137
+
138
+ columns: list[TicketsByState]
139
+ total_tickets: int
140
+
141
+
142
+ class BulkAcceptRequest(BaseModel):
143
+ """Schema for bulk accepting proposed tickets."""
144
+
145
+ ticket_ids: list[str] = Field(
146
+ ..., min_length=1, description="List of ticket IDs to accept"
147
+ )
148
+ goal_id: str | None = Field(
149
+ None,
150
+ description="If provided, validates all tickets belong to this goal",
151
+ )
152
+ actor_type: ActorType = Field(
153
+ default=ActorType.HUMAN, description="Actor performing the accept"
154
+ )
155
+ actor_id: str | None = Field(None, description="ID of the actor")
156
+ reason: str | None = Field(
157
+ default="Accepted from AI-generated proposal",
158
+ description="Reason for acceptance",
159
+ )
160
+ queue_first: bool = Field(
161
+ default=False,
162
+ description="If true, queue the first accepted ticket for execution",
163
+ )
164
+
165
+
166
+ class BulkAcceptResult(BaseModel):
167
+ """Result for a single ticket in bulk accept."""
168
+
169
+ ticket_id: str
170
+ success: bool
171
+ error: str | None = None
172
+
173
+
174
+ class BulkAcceptResponse(BaseModel):
175
+ """Response for bulk accept operation."""
176
+
177
+ accepted_ids: list[str] = Field(default_factory=list)
178
+ rejected: list[BulkAcceptResult] = Field(default_factory=list)
179
+ accepted_count: int
180
+ failed_count: int
181
+ queued_job_id: str | None = Field(
182
+ None,
183
+ description="Job ID if queue_first was true and first ticket was queued",
184
+ )
185
+ queued_ticket_id: str | None = Field(
186
+ None,
187
+ description="Ticket ID that was queued (first in request order)",
188
+ )
189
+
190
+
191
+ class BulkTransitionRequest(BaseModel):
192
+ """Schema for bulk state transition of multiple tickets."""
193
+
194
+ ticket_ids: list[str] = Field(
195
+ ..., min_length=1, description="List of ticket IDs to transition"
196
+ )
197
+ target_state: TicketState = Field(..., description="Target state for all tickets")
198
+ reason: str = Field(
199
+ default="Bulk transition",
200
+ description="Reason for the state transition",
201
+ )
202
+ actor_type: ActorType = Field(
203
+ default=ActorType.HUMAN,
204
+ description="Type of actor performing the transition",
205
+ )
206
+ actor_id: str | None = Field(
207
+ None, description="ID of the actor performing the transition"
208
+ )
209
+
210
+
211
+ class BulkTransitionResult(BaseModel):
212
+ """Result for a single ticket in a bulk transition."""
213
+
214
+ ticket_id: str
215
+ success: bool
216
+ error: str | None = None
217
+ from_state: str | None = None
218
+ to_state: str | None = None
219
+
220
+
221
+ class BulkTransitionResponse(BaseModel):
222
+ """Response for bulk state transition operation."""
223
+
224
+ results: list[BulkTransitionResult]
225
+ transitioned_count: int
226
+ failed_count: int
227
+
228
+
229
+ class TicketReorderRequest(BaseModel):
230
+ """Schema for reordering a ticket within a column."""
231
+
232
+ ticket_id: str = Field(..., description="Ticket ID to reorder")
233
+ new_index: int = Field(
234
+ ..., ge=0, description="New position index (0-based) within the column"
235
+ )
236
+ column_state: TicketState = Field(
237
+ ..., description="State column in which to reorder"
238
+ )
@@ -0,0 +1,72 @@
1
+ """Pydantic schemas for TicketEvent entity."""
2
+
3
+ from datetime import datetime
4
+ from typing import Any
5
+
6
+ from pydantic import BaseModel, computed_field
7
+
8
+ from app.state_machine import ActorType, EventType, TicketState
9
+
10
+
11
+ class TicketEventResponse(BaseModel):
12
+ """Schema for ticket event response."""
13
+
14
+ id: str
15
+ ticket_id: str
16
+ event_type: EventType
17
+ from_state: TicketState | None
18
+ to_state: TicketState | None
19
+ actor_type: ActorType
20
+ actor_id: str | None
21
+ reason: str | None
22
+ payload: dict[str, Any] | None
23
+ created_at: datetime
24
+
25
+ model_config = {"from_attributes": True}
26
+
27
+ @computed_field
28
+ @property
29
+ def event_type_display(self) -> str:
30
+ """Human-readable event type."""
31
+ display_map = {
32
+ EventType.CREATED: "Created",
33
+ EventType.TRANSITIONED: "Transitioned",
34
+ EventType.UPDATED: "Updated",
35
+ EventType.COMMENT: "Comment",
36
+ }
37
+ return display_map.get(self.event_type, self.event_type.value.title())
38
+
39
+ @computed_field
40
+ @property
41
+ def actor_type_display(self) -> str:
42
+ """Human-readable actor type."""
43
+ display_map = {
44
+ ActorType.HUMAN: "Human",
45
+ ActorType.PLANNER: "AI Planner",
46
+ ActorType.EXECUTOR: "AI Executor",
47
+ ActorType.SYSTEM: "System",
48
+ }
49
+ return display_map.get(self.actor_type, self.actor_type.value.title())
50
+
51
+ @computed_field
52
+ @property
53
+ def from_state_display(self) -> str | None:
54
+ """Human-readable from state."""
55
+ if self.from_state is None:
56
+ return None
57
+ return self.from_state.value.replace("_", " ").title()
58
+
59
+ @computed_field
60
+ @property
61
+ def to_state_display(self) -> str | None:
62
+ """Human-readable to state."""
63
+ if self.to_state is None:
64
+ return None
65
+ return self.to_state.value.replace("_", " ").title()
66
+
67
+
68
+ class TicketEventListResponse(BaseModel):
69
+ """Schema for list of ticket events response."""
70
+
71
+ events: list[TicketEventResponse]
72
+ total: int
@@ -0,0 +1,19 @@
1
+ """Pydantic schemas for Workspace API responses."""
2
+
3
+ from datetime import datetime
4
+
5
+ from pydantic import BaseModel, ConfigDict
6
+
7
+
8
+ class WorkspaceResponse(BaseModel):
9
+ """Response model for workspace information."""
10
+
11
+ model_config = ConfigDict(from_attributes=True)
12
+
13
+ id: str
14
+ ticket_id: str
15
+ worktree_path: str
16
+ branch_name: str
17
+ created_at: datetime
18
+ cleaned_up_at: datetime | None = None
19
+ is_active: bool
@@ -0,0 +1,31 @@
1
+ """Service layer for Draft business logic."""
2
+
3
+ from app.services.board_service import BoardService
4
+ from app.services.cleanup_service import CleanupService
5
+ from app.services.context_gatherer import ContextGatherer
6
+ from app.services.goal_service import GoalService
7
+ from app.services.job_service import JobService
8
+ from app.services.llm_service import LLMService
9
+ from app.services.merge_service import MergeService
10
+ from app.services.planner_service import PlannerService
11
+ from app.services.review_service import ReviewService
12
+ from app.services.revision_service import RevisionService
13
+ from app.services.ticket_generation_service import TicketGenerationService
14
+ from app.services.ticket_service import TicketService
15
+ from app.services.workspace_service import WorkspaceService
16
+
17
+ __all__ = [
18
+ "BoardService",
19
+ "CleanupService",
20
+ "ContextGatherer",
21
+ "GoalService",
22
+ "JobService",
23
+ "LLMService",
24
+ "MergeService",
25
+ "PlannerService",
26
+ "ReviewService",
27
+ "RevisionService",
28
+ "TicketGenerationService",
29
+ "TicketService",
30
+ "WorkspaceService",
31
+ ]