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,67 @@
1
+ """add_merge_checklist_table
2
+
3
+ Revision ID: 3348e5cf54c1
4
+ Revises: 8f3e2bd8ea3b
5
+ Create Date: 2026-01-29 12:40:59.627310
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 = "3348e5cf54c1"
17
+ down_revision: str | None = "8f3e2bd8ea3b"
18
+ branch_labels: str | Sequence[str] | None = None
19
+ depends_on: str | Sequence[str] | None = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ # Create merge_checklists table
24
+ op.create_table(
25
+ "merge_checklists",
26
+ sa.Column("id", sa.String(length=36), nullable=False),
27
+ sa.Column("goal_id", sa.String(length=36), nullable=False),
28
+ sa.Column("all_tests_passed", sa.Boolean(), nullable=False),
29
+ sa.Column("total_files_changed", sa.Integer(), nullable=False),
30
+ sa.Column("total_lines_changed", sa.Integer(), nullable=False),
31
+ sa.Column("total_cost_usd", sa.Float(), nullable=True),
32
+ sa.Column("budget_exceeded", sa.Boolean(), nullable=False),
33
+ sa.Column("code_reviewed", sa.Boolean(), nullable=False),
34
+ sa.Column("no_sensitive_data", sa.Boolean(), nullable=False),
35
+ sa.Column("rollback_plan_understood", sa.Boolean(), nullable=False),
36
+ sa.Column("documentation_updated", sa.Boolean(), nullable=False),
37
+ sa.Column("rollback_plan_json", sa.Text(), nullable=True),
38
+ sa.Column("risk_level", sa.String(length=20), nullable=False),
39
+ sa.Column("ready_to_merge", sa.Boolean(), nullable=False),
40
+ sa.Column("merged_at", sa.DateTime(), nullable=True),
41
+ sa.Column(
42
+ "created_at",
43
+ sa.DateTime(),
44
+ server_default=sa.text("(CURRENT_TIMESTAMP)"),
45
+ nullable=False,
46
+ ),
47
+ sa.Column(
48
+ "updated_at",
49
+ sa.DateTime(),
50
+ server_default=sa.text("(CURRENT_TIMESTAMP)"),
51
+ nullable=False,
52
+ ),
53
+ sa.ForeignKeyConstraint(["goal_id"], ["goals.id"], ondelete="CASCADE"),
54
+ sa.PrimaryKeyConstraint("id"),
55
+ )
56
+ op.create_index(
57
+ op.f("ix_merge_checklists_goal_id"),
58
+ "merge_checklists",
59
+ ["goal_id"],
60
+ unique=False,
61
+ )
62
+
63
+
64
+ def downgrade() -> None:
65
+ # Drop merge_checklists table
66
+ op.drop_index(op.f("ix_merge_checklists_goal_id"), table_name="merge_checklists")
67
+ op.drop_table("merge_checklists")
@@ -0,0 +1,34 @@
1
+ """add_goal_status
2
+
3
+ Revision ID: 357c780ee445
4
+ Revises: add_job_variant_001
5
+ Create Date: 2026-01-29 19:58:09.271091
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 = "357c780ee445"
17
+ down_revision: str | None = "add_job_variant_001"
18
+ branch_labels: str | Sequence[str] | None = None
19
+ depends_on: str | Sequence[str] | None = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ # Add status column to goals table
24
+ op.add_column(
25
+ "goals",
26
+ sa.Column(
27
+ "status", sa.String(length=20), nullable=False, server_default="proposed"
28
+ ),
29
+ )
30
+
31
+
32
+ def downgrade() -> None:
33
+ # Remove status column from goals table
34
+ op.drop_column("goals", "status")
@@ -0,0 +1,65 @@
1
+ """add autonomy fields to goal
2
+
3
+ Revision ID: 553340b7e26c
4
+ Revises: 7b307e847cbd
5
+ Create Date: 2026-02-16 13:31:00.783226
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 = "553340b7e26c"
17
+ down_revision: str | None = "7b307e847cbd"
18
+ branch_labels: str | Sequence[str] | None = None
19
+ depends_on: str | Sequence[str] | None = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ op.add_column(
24
+ "goals",
25
+ sa.Column("autonomy_enabled", sa.Boolean(), server_default="0", nullable=False),
26
+ )
27
+ op.add_column(
28
+ "goals",
29
+ sa.Column(
30
+ "auto_approve_tickets", sa.Boolean(), server_default="0", nullable=False
31
+ ),
32
+ )
33
+ op.add_column(
34
+ "goals",
35
+ sa.Column(
36
+ "auto_approve_revisions", sa.Boolean(), server_default="0", nullable=False
37
+ ),
38
+ )
39
+ op.add_column(
40
+ "goals",
41
+ sa.Column("auto_merge", sa.Boolean(), server_default="0", nullable=False),
42
+ )
43
+ op.add_column(
44
+ "goals",
45
+ sa.Column(
46
+ "auto_approve_followups", sa.Boolean(), server_default="0", nullable=False
47
+ ),
48
+ )
49
+ op.add_column("goals", sa.Column("max_auto_approvals", sa.Integer(), nullable=True))
50
+ op.add_column(
51
+ "goals",
52
+ sa.Column(
53
+ "auto_approval_count", sa.Integer(), server_default="0", nullable=False
54
+ ),
55
+ )
56
+
57
+
58
+ def downgrade() -> None:
59
+ op.drop_column("goals", "auto_approval_count")
60
+ op.drop_column("goals", "max_auto_approvals")
61
+ op.drop_column("goals", "auto_approve_followups")
62
+ op.drop_column("goals", "auto_merge")
63
+ op.drop_column("goals", "auto_approve_revisions")
64
+ op.drop_column("goals", "auto_approve_tickets")
65
+ op.drop_column("goals", "autonomy_enabled")
@@ -0,0 +1,23 @@
1
+ """merge migration heads
2
+
3
+ Revision ID: 774dc335c679
4
+ Revises: 553340b7e26c, a1b2c3d4e5f6
5
+ Create Date: 2026-02-17 06:14:05.708067
6
+
7
+ """
8
+
9
+ from collections.abc import Sequence
10
+
11
+ # revision identifiers, used by Alembic.
12
+ revision: str = "774dc335c679"
13
+ down_revision: str | None = ("553340b7e26c", "a1b2c3d4e5f6")
14
+ branch_labels: str | Sequence[str] | None = None
15
+ depends_on: str | Sequence[str] | None = None
16
+
17
+
18
+ def upgrade() -> None:
19
+ pass
20
+
21
+
22
+ def downgrade() -> None:
23
+ pass
@@ -0,0 +1,23 @@
1
+ """merge heads
2
+
3
+ Revision ID: 7b307e847cbd
4
+ Revises: add_agent_conversation_history, c3a8f1b2d4e5
5
+ Create Date: 2026-02-16 13:23:30.635840
6
+
7
+ """
8
+
9
+ from collections.abc import Sequence
10
+
11
+ # revision identifiers, used by Alembic.
12
+ revision: str = "7b307e847cbd"
13
+ down_revision: str | None = ("add_agent_conversation_history", "c3a8f1b2d4e5")
14
+ branch_labels: str | Sequence[str] | None = None
15
+ depends_on: str | Sequence[str] | None = None
16
+
17
+
18
+ def upgrade() -> None:
19
+ pass
20
+
21
+
22
+ def downgrade() -> None:
23
+ pass
@@ -0,0 +1,48 @@
1
+ """add_missing_indexes
2
+
3
+ Revision ID: 82ecd978cc70
4
+ Revises: 774dc335c679
5
+ Create Date: 2026-03-01 06:04:37.402741
6
+
7
+ """
8
+
9
+ from alembic import op
10
+
11
+ # revision identifiers, used by Alembic.
12
+ revision: str = "82ecd978cc70"
13
+ down_revision: str | None = "015"
14
+ branch_labels: str | None = None
15
+ depends_on: str | None = None
16
+
17
+
18
+ def upgrade() -> None:
19
+ """Add missing indexes for common query patterns.
20
+
21
+ Note: ix_goals_board_id and ix_revisions_job_id already exist
22
+ (created in migrations 012 and 007 respectively), so only the
23
+ composite indexes that are genuinely missing are added here.
24
+ """
25
+
26
+ # Composite index for workspace cleanup queries
27
+ # (find active/stale workspaces ordered by creation)
28
+ op.create_index(
29
+ "ix_workspaces_cleanup",
30
+ "workspaces",
31
+ ["cleaned_up_at", "created_at"],
32
+ unique=False,
33
+ )
34
+
35
+ # Composite index for rate limit lookups
36
+ # (find entries by client_key that haven't expired)
37
+ op.create_index(
38
+ "ix_rate_limit_entries_lookup",
39
+ "rate_limit_entries",
40
+ ["client_key", "expires_at"],
41
+ unique=False,
42
+ )
43
+
44
+
45
+ def downgrade() -> None:
46
+ """Remove added indexes."""
47
+ op.drop_index("ix_rate_limit_entries_lookup", table_name="rate_limit_entries")
48
+ op.drop_index("ix_workspaces_cleanup", table_name="workspaces")
@@ -0,0 +1,173 @@
1
+ """add_normalized_log_entries
2
+
3
+ Revision ID: 8ef5054dc280
4
+ Revises: 014
5
+ Create Date: 2026-01-12 10:47:40.188826
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 = "8ef5054dc280"
17
+ down_revision: str | None = "014"
18
+ branch_labels: str | Sequence[str] | None = None
19
+ depends_on: str | Sequence[str] | None = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ # ### commands auto generated by Alembic - please adjust! ###
24
+ # Create normalized_log_entries table
25
+ op.create_table(
26
+ "normalized_log_entries",
27
+ sa.Column("id", sa.String(length=36), nullable=False),
28
+ sa.Column("job_id", sa.String(length=36), nullable=False),
29
+ sa.Column("sequence", sa.Integer(), nullable=False),
30
+ sa.Column("timestamp", sa.DateTime(), nullable=False),
31
+ sa.Column(
32
+ "entry_type",
33
+ sa.Enum(
34
+ "THINKING",
35
+ "FILE_EDIT",
36
+ "FILE_CREATE",
37
+ "FILE_DELETE",
38
+ "COMMAND_RUN",
39
+ "TOOL_CALL",
40
+ "ERROR",
41
+ "USER_MESSAGE",
42
+ "SYSTEM_MESSAGE",
43
+ "LOADING",
44
+ name="logentrytype",
45
+ ),
46
+ nullable=False,
47
+ ),
48
+ sa.Column("content", sa.Text(), nullable=False),
49
+ sa.Column("entry_metadata", sa.JSON(), nullable=True),
50
+ sa.Column("collapsed", sa.Boolean(), nullable=True, default=False),
51
+ sa.Column("highlight", sa.Boolean(), nullable=True, default=False),
52
+ sa.ForeignKeyConstraint(["job_id"], ["jobs.id"], ondelete="CASCADE"),
53
+ sa.PrimaryKeyConstraint("id"),
54
+ )
55
+ op.create_index(
56
+ "ix_normalized_log_entries_job_id", "normalized_log_entries", ["job_id"]
57
+ )
58
+ op.create_index(
59
+ "ix_normalized_log_entries_job_sequence",
60
+ "normalized_log_entries",
61
+ ["job_id", "sequence"],
62
+ )
63
+ op.create_index(
64
+ "ix_normalized_log_entries_entry_type", "normalized_log_entries", ["entry_type"]
65
+ )
66
+
67
+ op.drop_index(op.f("ix_agent_sessions_agent_type"), table_name="agent_sessions")
68
+ op.drop_index(op.f("ix_agent_sessions_created_at"), table_name="agent_sessions")
69
+ op.alter_column(
70
+ "boards",
71
+ "created_at",
72
+ existing_type=sa.DATETIME(),
73
+ nullable=False,
74
+ existing_server_default=sa.text("(CURRENT_TIMESTAMP)"),
75
+ )
76
+ op.alter_column(
77
+ "boards",
78
+ "updated_at",
79
+ existing_type=sa.DATETIME(),
80
+ nullable=False,
81
+ existing_server_default=sa.text("(CURRENT_TIMESTAMP)"),
82
+ )
83
+ op.create_foreign_key(
84
+ None, "jobs", "revisions", ["source_revision_id"], ["id"], ondelete="SET NULL"
85
+ )
86
+ op.drop_column("jobs", "agent_session_id")
87
+ op.drop_column("jobs", "agent_type")
88
+ op.drop_index(
89
+ op.f("ix_review_comments_revision_resolved"), table_name="review_comments"
90
+ )
91
+ op.drop_index(
92
+ op.f("ix_review_summaries_revision_id"), table_name="review_summaries"
93
+ )
94
+ op.create_index(
95
+ op.f("ix_review_summaries_revision_id"),
96
+ "review_summaries",
97
+ ["revision_id"],
98
+ unique=True,
99
+ )
100
+ op.drop_column("tickets", "total_cost_usd")
101
+ op.drop_index(op.f("ix_workspaces_ticket_id"), table_name="workspaces")
102
+ op.create_index(
103
+ op.f("ix_workspaces_ticket_id"), "workspaces", ["ticket_id"], unique=True
104
+ )
105
+ # ### end Alembic commands ###
106
+
107
+
108
+ def downgrade() -> None:
109
+ # ### commands auto generated by Alembic - please adjust! ###
110
+ # Drop normalized_log_entries table
111
+ op.drop_index(
112
+ "ix_normalized_log_entries_entry_type", table_name="normalized_log_entries"
113
+ )
114
+ op.drop_index(
115
+ "ix_normalized_log_entries_job_sequence", table_name="normalized_log_entries"
116
+ )
117
+ op.drop_index(
118
+ "ix_normalized_log_entries_job_id", table_name="normalized_log_entries"
119
+ )
120
+ op.drop_table("normalized_log_entries")
121
+
122
+ op.drop_index(op.f("ix_workspaces_ticket_id"), table_name="workspaces")
123
+ op.create_index(
124
+ op.f("ix_workspaces_ticket_id"), "workspaces", ["ticket_id"], unique=False
125
+ )
126
+ op.add_column("tickets", sa.Column("total_cost_usd", sa.FLOAT(), nullable=True))
127
+ op.drop_index(
128
+ op.f("ix_review_summaries_revision_id"), table_name="review_summaries"
129
+ )
130
+ op.create_index(
131
+ op.f("ix_review_summaries_revision_id"),
132
+ "review_summaries",
133
+ ["revision_id"],
134
+ unique=False,
135
+ )
136
+ op.create_index(
137
+ op.f("ix_review_comments_revision_resolved"),
138
+ "review_comments",
139
+ ["revision_id", "resolved"],
140
+ unique=False,
141
+ )
142
+ op.add_column("jobs", sa.Column("agent_type", sa.VARCHAR(length=50), nullable=True))
143
+ op.add_column(
144
+ "jobs", sa.Column("agent_session_id", sa.VARCHAR(length=36), nullable=True)
145
+ )
146
+ op.drop_constraint(None, "jobs", type_="foreignkey")
147
+ op.alter_column(
148
+ "boards",
149
+ "updated_at",
150
+ existing_type=sa.DATETIME(),
151
+ nullable=True,
152
+ existing_server_default=sa.text("(CURRENT_TIMESTAMP)"),
153
+ )
154
+ op.alter_column(
155
+ "boards",
156
+ "created_at",
157
+ existing_type=sa.DATETIME(),
158
+ nullable=True,
159
+ existing_server_default=sa.text("(CURRENT_TIMESTAMP)"),
160
+ )
161
+ op.create_index(
162
+ op.f("ix_agent_sessions_created_at"),
163
+ "agent_sessions",
164
+ ["created_at"],
165
+ unique=False,
166
+ )
167
+ op.create_index(
168
+ op.f("ix_agent_sessions_agent_type"),
169
+ "agent_sessions",
170
+ ["agent_type"],
171
+ unique=False,
172
+ )
173
+ # ### end Alembic commands ###
@@ -0,0 +1,23 @@
1
+ """merge_migration_heads
2
+
3
+ Revision ID: 8f3e2bd8ea3b
4
+ Revises: 03220f0b93ae, perf_indexes_001
5
+ Create Date: 2026-01-29 12:39:52.200213
6
+
7
+ """
8
+
9
+ from collections.abc import Sequence
10
+
11
+ # revision identifiers, used by Alembic.
12
+ revision: str = "8f3e2bd8ea3b"
13
+ down_revision: str | None = ("03220f0b93ae", "perf_indexes_001")
14
+ branch_labels: str | Sequence[str] | None = None
15
+ depends_on: str | Sequence[str] | None = None
16
+
17
+
18
+ def upgrade() -> None:
19
+ pass
20
+
21
+
22
+ def downgrade() -> None:
23
+ pass
@@ -0,0 +1,30 @@
1
+ """Add config column to boards table
2
+
3
+ Revision ID: 9d17f0698d3b
4
+ Revises: add_repos_001
5
+ Create Date: 2026-02-03 17:11:54.526459
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 = "9d17f0698d3b"
17
+ down_revision: str | None = "add_repos_001"
18
+ branch_labels: str | Sequence[str] | None = None
19
+ depends_on: str | Sequence[str] | None = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ # Add config JSON column to boards table
24
+ # This allows board-level configuration overrides for draft.yaml settings
25
+ op.add_column("boards", sa.Column("config", sa.JSON(), nullable=True))
26
+
27
+
28
+ def downgrade() -> None:
29
+ # Remove config column
30
+ op.drop_column("boards", "config")
@@ -0,0 +1,72 @@
1
+ """Add agent_conversation_history table for UDAR agent memory
2
+
3
+ Revision ID: add_agent_conversation_history
4
+ Revises: b10fb0b62240
5
+ Create Date: 2026-02-09 19:00:00.000000
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 = "add_agent_conversation_history"
17
+ down_revision: str | None = "b10fb0b62240"
18
+ branch_labels: str | Sequence[str] | None = None
19
+ depends_on: str | Sequence[str] | None = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ """Create agent_conversation_history table for UDAR agent memory.
24
+
25
+ This table stores compressed conversation checkpoints (summaries, not full messages)
26
+ to support UDAR agent state persistence across multiple invocations.
27
+ """
28
+ op.create_table(
29
+ "agent_conversation_history",
30
+ sa.Column("id", sa.String(length=36), nullable=False),
31
+ sa.Column("goal_id", sa.String(length=36), nullable=False),
32
+ sa.Column("checkpoint_id", sa.String(length=100), nullable=False),
33
+ sa.Column(
34
+ "messages_json", sa.Text(), nullable=True
35
+ ), # Empty by default (lean storage)
36
+ sa.Column("metadata_json", sa.Text(), nullable=False), # Compressed summary
37
+ sa.Column("created_at", sa.DateTime(), nullable=False),
38
+ sa.Column("updated_at", sa.DateTime(), nullable=False),
39
+ sa.PrimaryKeyConstraint("id"),
40
+ sa.ForeignKeyConstraint(
41
+ ["goal_id"],
42
+ ["goals.id"],
43
+ name="fk_agent_conversation_history_goal_id",
44
+ ondelete="CASCADE", # Delete history when goal deleted
45
+ ),
46
+ )
47
+
48
+ # Index for efficient lookup by goal_id
49
+ op.create_index(
50
+ "ix_agent_conversation_history_goal_id",
51
+ "agent_conversation_history",
52
+ ["goal_id"],
53
+ )
54
+
55
+ # Index for cleanup queries (find old checkpoints)
56
+ op.create_index(
57
+ "ix_agent_conversation_history_created_at",
58
+ "agent_conversation_history",
59
+ ["created_at"],
60
+ )
61
+
62
+
63
+ def downgrade() -> None:
64
+ """Drop agent_conversation_history table."""
65
+ op.drop_index(
66
+ "ix_agent_conversation_history_created_at",
67
+ table_name="agent_conversation_history",
68
+ )
69
+ op.drop_index(
70
+ "ix_agent_conversation_history_goal_id", table_name="agent_conversation_history"
71
+ )
72
+ op.drop_table("agent_conversation_history")
@@ -0,0 +1,34 @@
1
+ """add_job_variant
2
+
3
+ Revision ID: add_job_variant_001
4
+ Revises: 3348e5cf54c1
5
+ Create Date: 2026-01-29
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 = "add_job_variant_001"
17
+ down_revision: str | None = "3348e5cf54c1"
18
+ branch_labels: str | Sequence[str] | None = None
19
+ depends_on: str | Sequence[str] | None = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ # Add variant column to jobs table
24
+ op.add_column(
25
+ "jobs",
26
+ sa.Column(
27
+ "variant", sa.String(length=20), nullable=True, server_default="default"
28
+ ),
29
+ )
30
+
31
+
32
+ def downgrade() -> None:
33
+ # Remove variant column from jobs table
34
+ op.drop_column("jobs", "variant")
@@ -0,0 +1,95 @@
1
+ """Add performance indexes for hot paths.
2
+
3
+ Revision ID: perf_indexes_001
4
+ Revises: 8ef5054dc280
5
+ Create Date: 2026-01-28
6
+
7
+ """
8
+
9
+ from alembic import op
10
+
11
+ # revision identifiers, used by Alembic.
12
+ revision = "perf_indexes_001"
13
+ down_revision = "8ef5054dc280"
14
+ branch_labels = None
15
+ depends_on = None
16
+
17
+
18
+ def upgrade():
19
+ """Add performance indexes for frequently queried paths."""
20
+
21
+ # Hot path: Get jobs by status and created_at (for queue status, watchdog)
22
+ op.create_index(
23
+ "idx_jobs_status_created",
24
+ "jobs",
25
+ ["status", "created_at"],
26
+ unique=False,
27
+ )
28
+
29
+ # Hot path: Get tickets by state and priority (for board view)
30
+ op.create_index(
31
+ "idx_tickets_state_priority",
32
+ "tickets",
33
+ ["state", "priority"],
34
+ unique=False,
35
+ )
36
+
37
+ # Hot path: Get open revisions for a ticket
38
+ op.create_index(
39
+ "idx_revisions_ticket_status",
40
+ "revisions",
41
+ ["ticket_id", "status"],
42
+ unique=False,
43
+ )
44
+
45
+ # Hot path: Find evidence by job_id and kind (for diffs)
46
+ op.create_index(
47
+ "idx_evidence_job_kind",
48
+ "evidence",
49
+ ["job_id", "kind"],
50
+ unique=False,
51
+ )
52
+
53
+ # Hot path: Job watchdog queries (find stale jobs by heartbeat)
54
+ op.create_index(
55
+ "idx_jobs_status_heartbeat",
56
+ "jobs",
57
+ ["status", "last_heartbeat_at"],
58
+ unique=False,
59
+ )
60
+
61
+ # Hot path: Get jobs for a ticket (ordered by creation)
62
+ op.create_index(
63
+ "idx_jobs_ticket_created",
64
+ "jobs",
65
+ ["ticket_id", "created_at"],
66
+ unique=False,
67
+ )
68
+
69
+ # Hot path: Get events for a ticket (audit log)
70
+ op.create_index(
71
+ "idx_ticket_events_ticket_created",
72
+ "ticket_events",
73
+ ["ticket_id", "created_at"],
74
+ unique=False,
75
+ )
76
+
77
+ # Hot path: Rate limiting queries (count recent jobs by ticket/kind/time)
78
+ op.create_index(
79
+ "idx_jobs_ticket_kind_created",
80
+ "jobs",
81
+ ["ticket_id", "kind", "created_at"],
82
+ unique=False,
83
+ )
84
+
85
+
86
+ def downgrade():
87
+ """Remove performance indexes."""
88
+ op.drop_index("idx_jobs_ticket_kind_created", table_name="jobs")
89
+ op.drop_index("idx_ticket_events_ticket_created", table_name="ticket_events")
90
+ op.drop_index("idx_jobs_ticket_created", table_name="jobs")
91
+ op.drop_index("idx_jobs_status_heartbeat", table_name="jobs")
92
+ op.drop_index("idx_evidence_job_kind", table_name="evidence")
93
+ op.drop_index("idx_revisions_ticket_status", table_name="revisions")
94
+ op.drop_index("idx_tickets_state_priority", table_name="tickets")
95
+ op.drop_index("idx_jobs_status_created", table_name="jobs")