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,28 @@
1
+ """${message}
2
+
3
+ Revision ID: ${up_revision}
4
+ Revises: ${down_revision | comma,n}
5
+ Create Date: ${create_date}
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+ ${imports if imports else ""}
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = ${repr(up_revision)}
16
+ down_revision: Union[str, None] = ${repr(down_revision)}
17
+ branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
18
+ depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
19
+
20
+
21
+ def upgrade() -> None:
22
+ ${upgrades if upgrades else "pass"}
23
+
24
+
25
+ def downgrade() -> None:
26
+ ${downgrades if downgrades else "pass"}
27
+
28
+
@@ -0,0 +1,104 @@
1
+ """Initial schema for goals, tickets, and ticket_events.
2
+
3
+ Revision ID: 001
4
+ Revises:
5
+ Create Date: 2026-01-05
6
+
7
+ """
8
+
9
+ from collections.abc import Sequence
10
+
11
+ import sqlalchemy as sa
12
+
13
+ from alembic import op
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision: str = "001"
17
+ down_revision: str | None = None
18
+ branch_labels: str | Sequence[str] | None = None
19
+ depends_on: str | Sequence[str] | None = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ # Create goals table
24
+ op.create_table(
25
+ "goals",
26
+ sa.Column("id", sa.String(36), primary_key=True),
27
+ sa.Column("title", sa.String(255), nullable=False),
28
+ sa.Column("description", sa.Text, nullable=True),
29
+ sa.Column(
30
+ "created_at",
31
+ sa.DateTime,
32
+ server_default=sa.func.now(),
33
+ nullable=False,
34
+ ),
35
+ sa.Column(
36
+ "updated_at",
37
+ sa.DateTime,
38
+ server_default=sa.func.now(),
39
+ nullable=False,
40
+ ),
41
+ )
42
+
43
+ # Create tickets table
44
+ op.create_table(
45
+ "tickets",
46
+ sa.Column("id", sa.String(36), primary_key=True),
47
+ sa.Column(
48
+ "goal_id",
49
+ sa.String(36),
50
+ sa.ForeignKey("goals.id", ondelete="CASCADE"),
51
+ nullable=False,
52
+ ),
53
+ sa.Column("title", sa.String(255), nullable=False),
54
+ sa.Column("description", sa.Text, nullable=True),
55
+ sa.Column("state", sa.String(50), nullable=False, default="proposed"),
56
+ sa.Column("priority", sa.Integer, nullable=True),
57
+ sa.Column(
58
+ "created_at",
59
+ sa.DateTime,
60
+ server_default=sa.func.now(),
61
+ nullable=False,
62
+ ),
63
+ sa.Column(
64
+ "updated_at",
65
+ sa.DateTime,
66
+ server_default=sa.func.now(),
67
+ nullable=False,
68
+ ),
69
+ )
70
+ op.create_index("ix_tickets_goal_id", "tickets", ["goal_id"])
71
+ op.create_index("ix_tickets_state", "tickets", ["state"])
72
+
73
+ # Create ticket_events table
74
+ op.create_table(
75
+ "ticket_events",
76
+ sa.Column("id", sa.String(36), primary_key=True),
77
+ sa.Column(
78
+ "ticket_id",
79
+ sa.String(36),
80
+ sa.ForeignKey("tickets.id", ondelete="CASCADE"),
81
+ nullable=False,
82
+ ),
83
+ sa.Column("event_type", sa.String(50), nullable=False),
84
+ sa.Column("from_state", sa.String(50), nullable=True),
85
+ sa.Column("to_state", sa.String(50), nullable=True),
86
+ sa.Column("actor_type", sa.String(50), nullable=False),
87
+ sa.Column("actor_id", sa.String(255), nullable=True),
88
+ sa.Column("reason", sa.Text, nullable=True),
89
+ sa.Column("payload_json", sa.Text, nullable=True),
90
+ sa.Column(
91
+ "created_at",
92
+ sa.DateTime,
93
+ server_default=sa.func.now(),
94
+ nullable=False,
95
+ ),
96
+ )
97
+ op.create_index("ix_ticket_events_ticket_id", "ticket_events", ["ticket_id"])
98
+ op.create_index("ix_ticket_events_created_at", "ticket_events", ["created_at"])
99
+
100
+
101
+ def downgrade() -> None:
102
+ op.drop_table("ticket_events")
103
+ op.drop_table("tickets")
104
+ op.drop_table("goals")
@@ -0,0 +1,52 @@
1
+ """Add jobs table for tracking long-running task executions.
2
+
3
+ Revision ID: 002
4
+ Revises: 001
5
+ Create Date: 2026-01-05
6
+
7
+ """
8
+
9
+ from collections.abc import Sequence
10
+
11
+ import sqlalchemy as sa
12
+
13
+ from alembic import op
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision: str = "002"
17
+ down_revision: str | None = "001"
18
+ branch_labels: str | Sequence[str] | None = None
19
+ depends_on: str | Sequence[str] | None = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ # Create jobs table
24
+ op.create_table(
25
+ "jobs",
26
+ sa.Column("id", sa.String(36), primary_key=True),
27
+ sa.Column(
28
+ "ticket_id",
29
+ sa.String(36),
30
+ sa.ForeignKey("tickets.id", ondelete="CASCADE"),
31
+ nullable=False,
32
+ ),
33
+ sa.Column("kind", sa.String(20), nullable=False),
34
+ sa.Column("status", sa.String(20), nullable=False, default="queued"),
35
+ sa.Column(
36
+ "created_at",
37
+ sa.DateTime,
38
+ server_default=sa.func.now(),
39
+ nullable=False,
40
+ ),
41
+ sa.Column("started_at", sa.DateTime, nullable=True),
42
+ sa.Column("finished_at", sa.DateTime, nullable=True),
43
+ sa.Column("exit_code", sa.Integer, nullable=True),
44
+ sa.Column("log_path", sa.Text, nullable=True),
45
+ sa.Column("celery_task_id", sa.String(255), nullable=True),
46
+ )
47
+ op.create_index("ix_jobs_ticket_id", "jobs", ["ticket_id"])
48
+ op.create_index("ix_jobs_status", "jobs", ["status"])
49
+
50
+
51
+ def downgrade() -> None:
52
+ op.drop_table("jobs")
@@ -0,0 +1,48 @@
1
+ """Add workspaces table for git worktree isolation.
2
+
3
+ Revision ID: 003
4
+ Revises: 002
5
+ Create Date: 2026-01-05
6
+
7
+ """
8
+
9
+ from collections.abc import Sequence
10
+
11
+ import sqlalchemy as sa
12
+
13
+ from alembic import op
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision: str = "003"
17
+ down_revision: str | None = "002"
18
+ branch_labels: str | Sequence[str] | None = None
19
+ depends_on: str | Sequence[str] | None = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ # Create workspaces table
24
+ op.create_table(
25
+ "workspaces",
26
+ sa.Column("id", sa.String(36), primary_key=True),
27
+ sa.Column(
28
+ "ticket_id",
29
+ sa.String(36),
30
+ sa.ForeignKey("tickets.id", ondelete="CASCADE"),
31
+ nullable=False,
32
+ unique=True,
33
+ ),
34
+ sa.Column("worktree_path", sa.Text, nullable=False),
35
+ sa.Column("branch_name", sa.Text, nullable=False),
36
+ sa.Column(
37
+ "created_at",
38
+ sa.DateTime,
39
+ server_default=sa.func.now(),
40
+ nullable=False,
41
+ ),
42
+ sa.Column("cleaned_up_at", sa.DateTime, nullable=True),
43
+ )
44
+ op.create_index("ix_workspaces_ticket_id", "workspaces", ["ticket_id"])
45
+
46
+
47
+ def downgrade() -> None:
48
+ op.drop_table("workspaces")
@@ -0,0 +1,56 @@
1
+ """Add evidence table for verification command results.
2
+
3
+ Revision ID: 004
4
+ Revises: 003
5
+ Create Date: 2026-01-05
6
+
7
+ """
8
+
9
+ from collections.abc import Sequence
10
+
11
+ import sqlalchemy as sa
12
+
13
+ from alembic import op
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision: str = "004"
17
+ down_revision: str | None = "003"
18
+ branch_labels: str | Sequence[str] | None = None
19
+ depends_on: str | Sequence[str] | None = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ # Create evidence table
24
+ op.create_table(
25
+ "evidence",
26
+ sa.Column("id", sa.String(36), primary_key=True),
27
+ sa.Column(
28
+ "ticket_id",
29
+ sa.String(36),
30
+ sa.ForeignKey("tickets.id", ondelete="CASCADE"),
31
+ nullable=False,
32
+ ),
33
+ sa.Column(
34
+ "job_id",
35
+ sa.String(36),
36
+ sa.ForeignKey("jobs.id", ondelete="CASCADE"),
37
+ nullable=False,
38
+ ),
39
+ sa.Column("kind", sa.String(50), nullable=False),
40
+ sa.Column("command", sa.Text, nullable=False),
41
+ sa.Column("exit_code", sa.Integer, nullable=False),
42
+ sa.Column("stdout_path", sa.Text, nullable=True),
43
+ sa.Column("stderr_path", sa.Text, nullable=True),
44
+ sa.Column(
45
+ "created_at",
46
+ sa.DateTime,
47
+ server_default=sa.func.now(),
48
+ nullable=False,
49
+ ),
50
+ )
51
+ op.create_index("ix_evidence_ticket_id", "evidence", ["ticket_id"])
52
+ op.create_index("ix_evidence_job_id", "evidence", ["job_id"])
53
+
54
+
55
+ def downgrade() -> None:
56
+ op.drop_table("evidence")
@@ -0,0 +1,32 @@
1
+ """Add verification_commands column to tickets.
2
+
3
+ Revision ID: 005
4
+ Revises: 004
5
+ Create Date: 2026-01-06
6
+
7
+ """
8
+
9
+ from collections.abc import Sequence
10
+
11
+ import sqlalchemy as sa
12
+
13
+ from alembic import op
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision: str = "005"
17
+ down_revision: str | None = "004"
18
+ branch_labels: str | Sequence[str] | None = None
19
+ depends_on: str | Sequence[str] | None = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ # Add verification_commands_json column to tickets
24
+ # Stores JSON array of verification command strings
25
+ op.add_column(
26
+ "tickets",
27
+ sa.Column("verification_commands_json", sa.Text, nullable=True),
28
+ )
29
+
30
+
31
+ def downgrade() -> None:
32
+ op.drop_column("tickets", "verification_commands_json")
@@ -0,0 +1,39 @@
1
+ """Add planner lock table.
2
+
3
+ Revision ID: 006
4
+ Revises: 005
5
+ Create Date: 2026-01-06
6
+
7
+ """
8
+
9
+ from collections.abc import Sequence
10
+
11
+ import sqlalchemy as sa
12
+
13
+ from alembic import op
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision: str = "006"
17
+ down_revision: str | None = "005"
18
+ branch_labels: str | Sequence[str] | None = None
19
+ depends_on: str | Sequence[str] | None = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ """Create planner_locks table."""
24
+ op.create_table(
25
+ "planner_locks",
26
+ sa.Column("lock_key", sa.String(50), primary_key=True),
27
+ sa.Column(
28
+ "acquired_at",
29
+ sa.DateTime(),
30
+ server_default=sa.func.now(),
31
+ nullable=False,
32
+ ),
33
+ sa.Column("owner_id", sa.String(255), nullable=True),
34
+ )
35
+
36
+
37
+ def downgrade() -> None:
38
+ """Drop planner_locks table."""
39
+ op.drop_table("planner_locks")
@@ -0,0 +1,126 @@
1
+ """Add revision and review tables for PR-like human review loop.
2
+
3
+ Revision ID: 007
4
+ Revises: 006
5
+ Create Date: 2026-01-06
6
+
7
+ """
8
+
9
+ from collections.abc import Sequence
10
+
11
+ import sqlalchemy as sa
12
+
13
+ from alembic import op
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision: str = "007"
17
+ down_revision: str | None = "006"
18
+ branch_labels: str | Sequence[str] | None = None
19
+ depends_on: str | Sequence[str] | None = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ # Create revisions table
24
+ op.create_table(
25
+ "revisions",
26
+ sa.Column("id", sa.String(36), primary_key=True),
27
+ sa.Column(
28
+ "ticket_id",
29
+ sa.String(36),
30
+ sa.ForeignKey("tickets.id", ondelete="CASCADE"),
31
+ nullable=False,
32
+ ),
33
+ sa.Column(
34
+ "job_id",
35
+ sa.String(36),
36
+ sa.ForeignKey("jobs.id", ondelete="CASCADE"),
37
+ nullable=False,
38
+ ),
39
+ sa.Column("number", sa.Integer, nullable=False),
40
+ sa.Column("status", sa.String(50), nullable=False, server_default="open"),
41
+ sa.Column(
42
+ "diff_stat_evidence_id",
43
+ sa.String(36),
44
+ sa.ForeignKey("evidence.id", ondelete="SET NULL"),
45
+ nullable=True,
46
+ ),
47
+ sa.Column(
48
+ "diff_patch_evidence_id",
49
+ sa.String(36),
50
+ sa.ForeignKey("evidence.id", ondelete="SET NULL"),
51
+ nullable=True,
52
+ ),
53
+ sa.Column(
54
+ "created_at",
55
+ sa.DateTime,
56
+ server_default=sa.func.now(),
57
+ nullable=False,
58
+ ),
59
+ sa.UniqueConstraint("ticket_id", "number", name="uq_revision_ticket_number"),
60
+ )
61
+ op.create_index("ix_revisions_ticket_id", "revisions", ["ticket_id"])
62
+ op.create_index("ix_revisions_job_id", "revisions", ["job_id"])
63
+ op.create_index("ix_revisions_status", "revisions", ["status"])
64
+
65
+ # Create review_comments table
66
+ op.create_table(
67
+ "review_comments",
68
+ sa.Column("id", sa.String(36), primary_key=True),
69
+ sa.Column(
70
+ "revision_id",
71
+ sa.String(36),
72
+ sa.ForeignKey("revisions.id", ondelete="CASCADE"),
73
+ nullable=False,
74
+ ),
75
+ sa.Column("file_path", sa.String(500), nullable=False),
76
+ sa.Column("line_number", sa.Integer, nullable=False),
77
+ sa.Column("anchor", sa.String(40), nullable=False),
78
+ sa.Column("body", sa.Text, nullable=False),
79
+ sa.Column("author_type", sa.String(20), nullable=False, server_default="human"),
80
+ sa.Column("resolved", sa.Boolean, nullable=False, server_default=sa.false()),
81
+ sa.Column(
82
+ "created_at",
83
+ sa.DateTime,
84
+ server_default=sa.func.now(),
85
+ nullable=False,
86
+ ),
87
+ )
88
+ op.create_index(
89
+ "ix_review_comments_revision_id", "review_comments", ["revision_id"]
90
+ )
91
+ op.create_index("ix_review_comments_anchor", "review_comments", ["anchor"])
92
+ op.create_index(
93
+ "ix_review_comments_revision_resolved",
94
+ "review_comments",
95
+ ["revision_id", "resolved"],
96
+ )
97
+
98
+ # Create review_summaries table
99
+ op.create_table(
100
+ "review_summaries",
101
+ sa.Column("id", sa.String(36), primary_key=True),
102
+ sa.Column(
103
+ "revision_id",
104
+ sa.String(36),
105
+ sa.ForeignKey("revisions.id", ondelete="CASCADE"),
106
+ nullable=False,
107
+ unique=True,
108
+ ),
109
+ sa.Column("decision", sa.String(30), nullable=False),
110
+ sa.Column("body", sa.Text, nullable=False),
111
+ sa.Column(
112
+ "created_at",
113
+ sa.DateTime,
114
+ server_default=sa.func.now(),
115
+ nullable=False,
116
+ ),
117
+ )
118
+ op.create_index(
119
+ "ix_review_summaries_revision_id", "review_summaries", ["revision_id"]
120
+ )
121
+
122
+
123
+ def downgrade() -> None:
124
+ op.drop_table("review_summaries")
125
+ op.drop_table("review_comments")
126
+ op.drop_table("revisions")
@@ -0,0 +1,52 @@
1
+ """Add revision idempotency constraint and job source_revision_id for traceability.
2
+
3
+ Revision ID: 008
4
+ Revises: 007
5
+ Create Date: 2026-01-06
6
+
7
+ """
8
+
9
+ from collections.abc import Sequence
10
+
11
+ import sqlalchemy as sa
12
+
13
+ from alembic import op
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision: str = "008"
17
+ down_revision: str | None = "007"
18
+ branch_labels: str | Sequence[str] | None = None
19
+ depends_on: str | Sequence[str] | None = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ # Add unique constraint on (ticket_id, job_id) for revision idempotency
24
+ # This prevents duplicate revisions if the same job is retried
25
+ # Use batch mode for SQLite compatibility
26
+ with op.batch_alter_table("revisions") as batch_op:
27
+ batch_op.create_unique_constraint(
28
+ "uq_revision_ticket_job",
29
+ ["ticket_id", "job_id"],
30
+ )
31
+
32
+ # Add source_revision_id to jobs for traceability
33
+ # When a job is triggered by review feedback, this tracks which revision is being addressed
34
+ with op.batch_alter_table("jobs") as batch_op:
35
+ batch_op.add_column(
36
+ sa.Column(
37
+ "source_revision_id",
38
+ sa.String(36),
39
+ nullable=True,
40
+ ),
41
+ )
42
+ batch_op.create_index("ix_jobs_source_revision_id", ["source_revision_id"])
43
+ # Note: SQLite doesn't support FK constraints after table creation via ALTER
44
+ # The FK is defined in the model but not enforced at DB level for existing tables
45
+
46
+
47
+ def downgrade() -> None:
48
+ with op.batch_alter_table("jobs") as batch_op:
49
+ batch_op.drop_index("ix_jobs_source_revision_id")
50
+ batch_op.drop_column("source_revision_id")
51
+ with op.batch_alter_table("revisions") as batch_op:
52
+ batch_op.drop_constraint("uq_revision_ticket_job", type_="unique")
@@ -0,0 +1,46 @@
1
+ """Add job health monitoring fields.
2
+
3
+ Revision ID: 009
4
+ Revises: 008
5
+ Create Date: 2026-01-06
6
+
7
+ """
8
+
9
+ from collections.abc import Sequence
10
+
11
+ import sqlalchemy as sa
12
+
13
+ from alembic import op
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision: str = "009"
17
+ down_revision: str | None = "008"
18
+ branch_labels: str | Sequence[str] | None = None
19
+ depends_on: str | Sequence[str] | None = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ # Add last_heartbeat_at and timeout_seconds to jobs table
24
+ with op.batch_alter_table("jobs") as batch_op:
25
+ batch_op.add_column(
26
+ sa.Column(
27
+ "last_heartbeat_at",
28
+ sa.DateTime(),
29
+ nullable=True,
30
+ ),
31
+ )
32
+ batch_op.add_column(
33
+ sa.Column(
34
+ "timeout_seconds",
35
+ sa.Integer(),
36
+ nullable=True,
37
+ ),
38
+ )
39
+ batch_op.create_index("ix_jobs_last_heartbeat_at", ["last_heartbeat_at"])
40
+
41
+
42
+ def downgrade() -> None:
43
+ with op.batch_alter_table("jobs") as batch_op:
44
+ batch_op.drop_index("ix_jobs_last_heartbeat_at")
45
+ batch_op.drop_column("timeout_seconds")
46
+ batch_op.drop_column("last_heartbeat_at")
@@ -0,0 +1,36 @@
1
+ """Add line_content to review_comments.
2
+
3
+ Revision ID: 010
4
+ Revises: 009
5
+ Create Date: 2026-01-07
6
+
7
+ """
8
+
9
+ from collections.abc import Sequence
10
+
11
+ import sqlalchemy as sa
12
+
13
+ from alembic import op
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision: str = "010"
17
+ down_revision: str | None = "009"
18
+ branch_labels: str | Sequence[str] | None = None
19
+ depends_on: str | Sequence[str] | None = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ # Add line_content column to review_comments table
24
+ with op.batch_alter_table("review_comments") as batch_op:
25
+ batch_op.add_column(
26
+ sa.Column(
27
+ "line_content",
28
+ sa.Text(),
29
+ nullable=True,
30
+ ),
31
+ )
32
+
33
+
34
+ def downgrade() -> None:
35
+ with op.batch_alter_table("review_comments") as batch_op:
36
+ batch_op.drop_column("line_content")
@@ -0,0 +1,47 @@
1
+ """Add analysis_cache table for caching codebase analysis results.
2
+
3
+ Revision ID: 011
4
+ Revises: 010
5
+ Create Date: 2026-01-08
6
+ """
7
+
8
+ from collections.abc import Sequence
9
+
10
+ import sqlalchemy as sa
11
+
12
+ from alembic import op
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = "011"
16
+ down_revision: str | None = "010"
17
+ branch_labels: str | Sequence[str] | None = None
18
+ depends_on: str | Sequence[str] | None = None
19
+
20
+
21
+ def upgrade() -> None:
22
+ """Create analysis_cache table."""
23
+ op.create_table(
24
+ "analysis_cache",
25
+ sa.Column("id", sa.String(64), primary_key=True),
26
+ sa.Column("result_json", sa.Text(), nullable=False),
27
+ sa.Column(
28
+ "created_at",
29
+ sa.DateTime(),
30
+ server_default=sa.func.now(),
31
+ nullable=False,
32
+ ),
33
+ sa.Column("expires_at", sa.DateTime(), nullable=False),
34
+ )
35
+
36
+ # Index for cleanup queries
37
+ op.create_index(
38
+ "ix_analysis_cache_expires_at",
39
+ "analysis_cache",
40
+ ["expires_at"],
41
+ )
42
+
43
+
44
+ def downgrade() -> None:
45
+ """Drop analysis_cache table."""
46
+ op.drop_index("ix_analysis_cache_expires_at", table_name="analysis_cache")
47
+ op.drop_table("analysis_cache")