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.
- package/app/backend/.env.example +9 -0
- package/app/backend/.smartkanban/evidence/8b383839-cbec-45af-86ee-c7708d075cbe/bddf2ed5-2e21-4d46-a62b-10b87f1642a6_patch.txt +195 -0
- package/app/backend/.smartkanban/evidence/8b383839-cbec-45af-86ee-c7708d075cbe/bddf2ed5-2e21-4d46-a62b-10b87f1642a6_stat.txt +6 -0
- package/app/backend/CURL_EXAMPLES.md +335 -0
- package/app/backend/ENV_SETUP.md +65 -0
- package/app/backend/alembic/env.py +71 -0
- package/app/backend/alembic/script.py.mako +28 -0
- package/app/backend/alembic/versions/001_initial_schema.py +104 -0
- package/app/backend/alembic/versions/002_add_jobs_table.py +52 -0
- package/app/backend/alembic/versions/003_add_workspace_table.py +48 -0
- package/app/backend/alembic/versions/004_add_evidence_table.py +56 -0
- package/app/backend/alembic/versions/005_add_verification_commands.py +32 -0
- package/app/backend/alembic/versions/006_add_planner_lock_table.py +39 -0
- package/app/backend/alembic/versions/007_add_revision_review_tables.py +126 -0
- package/app/backend/alembic/versions/008_add_revision_idempotency_and_traceability.py +52 -0
- package/app/backend/alembic/versions/009_add_job_health_fields.py +46 -0
- package/app/backend/alembic/versions/010_add_review_comment_line_content.py +36 -0
- package/app/backend/alembic/versions/011_add_analysis_cache.py +47 -0
- package/app/backend/alembic/versions/012_add_boards_table.py +102 -0
- package/app/backend/alembic/versions/013_add_ticket_blocking.py +45 -0
- package/app/backend/alembic/versions/014_add_agent_sessions.py +220 -0
- package/app/backend/alembic/versions/015_add_ticket_sort_order.py +33 -0
- package/app/backend/alembic/versions/03220f0b93ae_add_pr_fields_to_ticket.py +49 -0
- package/app/backend/alembic/versions/0c2d89fff3b1_seed_board_configs_from_yaml.py +206 -0
- package/app/backend/alembic/versions/3348e5cf54c1_add_merge_checklist_table.py +67 -0
- package/app/backend/alembic/versions/357c780ee445_add_goal_status.py +34 -0
- package/app/backend/alembic/versions/553340b7e26c_add_autonomy_fields_to_goal.py +65 -0
- package/app/backend/alembic/versions/774dc335c679_merge_migration_heads.py +23 -0
- package/app/backend/alembic/versions/7b307e847cbd_merge_heads.py +23 -0
- package/app/backend/alembic/versions/82ecd978cc70_add_missing_indexes.py +48 -0
- package/app/backend/alembic/versions/8ef5054dc280_add_normalized_log_entries.py +173 -0
- package/app/backend/alembic/versions/8f3e2bd8ea3b_merge_migration_heads.py +23 -0
- package/app/backend/alembic/versions/9d17f0698d3b_add_config_column_to_boards_table.py +30 -0
- package/app/backend/alembic/versions/add_agent_conversation_history.py +72 -0
- package/app/backend/alembic/versions/add_job_variant.py +34 -0
- package/app/backend/alembic/versions/add_performance_indexes.py +95 -0
- package/app/backend/alembic/versions/add_repos_and_board_repos.py +174 -0
- package/app/backend/alembic/versions/add_session_id_to_jobs.py +27 -0
- package/app/backend/alembic/versions/add_sqlite_backend_tables.py +104 -0
- package/app/backend/alembic/versions/b10fb0b62240_add_diff_content_to_revisions.py +34 -0
- package/app/backend/alembic.ini +89 -0
- package/app/backend/app/__init__.py +3 -0
- package/app/backend/app/data_dir.py +85 -0
- package/app/backend/app/database.py +70 -0
- package/app/backend/app/database_sync.py +64 -0
- package/app/backend/app/dependencies/__init__.py +5 -0
- package/app/backend/app/dependencies/auth.py +80 -0
- package/app/backend/app/dependencies.py +43 -0
- package/app/backend/app/exceptions.py +178 -0
- package/app/backend/app/executors/__init__.py +1 -0
- package/app/backend/app/executors/adapters/__init__.py +1 -0
- package/app/backend/app/executors/adapters/aider.py +152 -0
- package/app/backend/app/executors/adapters/amazon_q.py +103 -0
- package/app/backend/app/executors/adapters/amp.py +123 -0
- package/app/backend/app/executors/adapters/claude.py +177 -0
- package/app/backend/app/executors/adapters/cline.py +127 -0
- package/app/backend/app/executors/adapters/codex.py +167 -0
- package/app/backend/app/executors/adapters/copilot.py +202 -0
- package/app/backend/app/executors/adapters/cursor.py +87 -0
- package/app/backend/app/executors/adapters/droid.py +123 -0
- package/app/backend/app/executors/adapters/gemini.py +132 -0
- package/app/backend/app/executors/adapters/goose.py +131 -0
- package/app/backend/app/executors/adapters/opencode.py +123 -0
- package/app/backend/app/executors/adapters/qwen.py +123 -0
- package/app/backend/app/executors/plugins/__init__.py +1 -0
- package/app/backend/app/executors/registry.py +202 -0
- package/app/backend/app/executors/spec.py +226 -0
- package/app/backend/app/main.py +486 -0
- package/app/backend/app/middleware/__init__.py +13 -0
- package/app/backend/app/middleware/idempotency.py +426 -0
- package/app/backend/app/middleware/rate_limit.py +312 -0
- package/app/backend/app/middleware/security_headers.py +43 -0
- package/app/backend/app/middleware/timeout.py +37 -0
- package/app/backend/app/models/__init__.py +56 -0
- package/app/backend/app/models/agent_conversation_history.py +56 -0
- package/app/backend/app/models/agent_session.py +127 -0
- package/app/backend/app/models/analysis_cache.py +49 -0
- package/app/backend/app/models/base.py +9 -0
- package/app/backend/app/models/board.py +79 -0
- package/app/backend/app/models/board_repo.py +68 -0
- package/app/backend/app/models/cost_budget.py +42 -0
- package/app/backend/app/models/enums.py +40 -0
- package/app/backend/app/models/evidence.py +132 -0
- package/app/backend/app/models/goal.py +102 -0
- package/app/backend/app/models/idempotency_entry.py +30 -0
- package/app/backend/app/models/job.py +163 -0
- package/app/backend/app/models/job_queue.py +39 -0
- package/app/backend/app/models/kv_store.py +28 -0
- package/app/backend/app/models/merge_checklist.py +87 -0
- package/app/backend/app/models/normalized_log.py +100 -0
- package/app/backend/app/models/planner_lock.py +43 -0
- package/app/backend/app/models/rate_limit_entry.py +25 -0
- package/app/backend/app/models/repo.py +66 -0
- package/app/backend/app/models/review_comment.py +91 -0
- package/app/backend/app/models/review_summary.py +69 -0
- package/app/backend/app/models/revision.py +130 -0
- package/app/backend/app/models/ticket.py +223 -0
- package/app/backend/app/models/ticket_event.py +83 -0
- package/app/backend/app/models/user.py +47 -0
- package/app/backend/app/models/workspace.py +71 -0
- package/app/backend/app/redis_client.py +119 -0
- package/app/backend/app/routers/__init__.py +29 -0
- package/app/backend/app/routers/agents.py +296 -0
- package/app/backend/app/routers/auth.py +94 -0
- package/app/backend/app/routers/board.py +885 -0
- package/app/backend/app/routers/dashboard.py +351 -0
- package/app/backend/app/routers/debug.py +528 -0
- package/app/backend/app/routers/evidence.py +96 -0
- package/app/backend/app/routers/executors.py +324 -0
- package/app/backend/app/routers/goals.py +574 -0
- package/app/backend/app/routers/jobs.py +448 -0
- package/app/backend/app/routers/maintenance.py +172 -0
- package/app/backend/app/routers/merge.py +360 -0
- package/app/backend/app/routers/planner.py +537 -0
- package/app/backend/app/routers/pull_requests.py +382 -0
- package/app/backend/app/routers/repos.py +263 -0
- package/app/backend/app/routers/revisions.py +939 -0
- package/app/backend/app/routers/settings.py +267 -0
- package/app/backend/app/routers/tickets.py +2003 -0
- package/app/backend/app/routers/webhooks.py +143 -0
- package/app/backend/app/routers/websocket.py +249 -0
- package/app/backend/app/schemas/__init__.py +109 -0
- package/app/backend/app/schemas/board.py +87 -0
- package/app/backend/app/schemas/common.py +33 -0
- package/app/backend/app/schemas/evidence.py +87 -0
- package/app/backend/app/schemas/goal.py +90 -0
- package/app/backend/app/schemas/job.py +97 -0
- package/app/backend/app/schemas/merge.py +139 -0
- package/app/backend/app/schemas/planner.py +500 -0
- package/app/backend/app/schemas/repo.py +187 -0
- package/app/backend/app/schemas/review.py +137 -0
- package/app/backend/app/schemas/revision.py +114 -0
- package/app/backend/app/schemas/ticket.py +238 -0
- package/app/backend/app/schemas/ticket_event.py +72 -0
- package/app/backend/app/schemas/workspace.py +19 -0
- package/app/backend/app/services/__init__.py +31 -0
- package/app/backend/app/services/agent_memory_service.py +223 -0
- package/app/backend/app/services/agent_registry.py +346 -0
- package/app/backend/app/services/agent_session_manager.py +318 -0
- package/app/backend/app/services/agent_session_service.py +219 -0
- package/app/backend/app/services/agent_tools.py +379 -0
- package/app/backend/app/services/auth_service.py +98 -0
- package/app/backend/app/services/autonomy_service.py +380 -0
- package/app/backend/app/services/board_repo_service.py +201 -0
- package/app/backend/app/services/board_service.py +326 -0
- package/app/backend/app/services/cleanup_service.py +1085 -0
- package/app/backend/app/services/config_service.py +908 -0
- package/app/backend/app/services/context_gatherer.py +557 -0
- package/app/backend/app/services/cost_tracking_service.py +293 -0
- package/app/backend/app/services/cursor_log_normalizer.py +536 -0
- package/app/backend/app/services/delivery_pipeline.py +440 -0
- package/app/backend/app/services/executor_service.py +634 -0
- package/app/backend/app/services/git_host/__init__.py +11 -0
- package/app/backend/app/services/git_host/factory.py +87 -0
- package/app/backend/app/services/git_host/github.py +270 -0
- package/app/backend/app/services/git_host/gitlab.py +194 -0
- package/app/backend/app/services/git_host/protocol.py +75 -0
- package/app/backend/app/services/git_merge_simple.py +346 -0
- package/app/backend/app/services/git_ops.py +384 -0
- package/app/backend/app/services/github_service.py +233 -0
- package/app/backend/app/services/goal_service.py +113 -0
- package/app/backend/app/services/job_service.py +423 -0
- package/app/backend/app/services/job_watchdog_service.py +424 -0
- package/app/backend/app/services/langchain_adapter.py +122 -0
- package/app/backend/app/services/llm_provider_clients.py +351 -0
- package/app/backend/app/services/llm_service.py +285 -0
- package/app/backend/app/services/log_normalizer.py +342 -0
- package/app/backend/app/services/log_stream_service.py +276 -0
- package/app/backend/app/services/merge_checklist_service.py +264 -0
- package/app/backend/app/services/merge_service.py +784 -0
- package/app/backend/app/services/orchestrator_log.py +84 -0
- package/app/backend/app/services/planner_service.py +1662 -0
- package/app/backend/app/services/planner_tick_sync.py +1040 -0
- package/app/backend/app/services/queued_message_service.py +156 -0
- package/app/backend/app/services/reliability_wrapper.py +389 -0
- package/app/backend/app/services/repo_discovery_service.py +318 -0
- package/app/backend/app/services/review_service.py +334 -0
- package/app/backend/app/services/revision_service.py +389 -0
- package/app/backend/app/services/safe_autopilot.py +510 -0
- package/app/backend/app/services/sqlite_worker.py +372 -0
- package/app/backend/app/services/task_dispatch.py +135 -0
- package/app/backend/app/services/ticket_generation_service.py +1781 -0
- package/app/backend/app/services/ticket_service.py +486 -0
- package/app/backend/app/services/udar_planner_service.py +1007 -0
- package/app/backend/app/services/webhook_service.py +126 -0
- package/app/backend/app/services/workspace_service.py +465 -0
- package/app/backend/app/services/worktree_file_service.py +92 -0
- package/app/backend/app/services/worktree_validator.py +213 -0
- package/app/backend/app/sqlite_kv.py +278 -0
- package/app/backend/app/state_machine.py +128 -0
- package/app/backend/app/templates/__init__.py +5 -0
- package/app/backend/app/templates/registry.py +243 -0
- package/app/backend/app/utils/__init__.py +5 -0
- package/app/backend/app/utils/artifact_reader.py +87 -0
- package/app/backend/app/utils/circuit_breaker.py +229 -0
- package/app/backend/app/utils/db_retry.py +136 -0
- package/app/backend/app/utils/ignored_fields.py +123 -0
- package/app/backend/app/utils/validators.py +54 -0
- package/app/backend/app/websocket/__init__.py +5 -0
- package/app/backend/app/websocket/manager.py +179 -0
- package/app/backend/app/websocket/state_tracker.py +113 -0
- package/app/backend/app/worker.py +3190 -0
- package/app/backend/calculator_tickets.json +40 -0
- package/app/backend/canary_tests.sh +591 -0
- package/app/backend/celerybeat-schedule +0 -0
- package/app/backend/celerybeat-schedule-shm +0 -0
- package/app/backend/celerybeat-schedule-wal +0 -0
- package/app/backend/logs/.gitkeep +3 -0
- package/app/backend/multiplication_division_implementation_tickets.json +55 -0
- package/app/backend/multiplication_division_tickets.json +42 -0
- package/app/backend/pyproject.toml +45 -0
- package/app/backend/requirements-dev.txt +8 -0
- package/app/backend/requirements.txt +20 -0
- package/app/backend/run.sh +30 -0
- package/app/backend/run_with_logs.sh +10 -0
- package/app/backend/scientific_calculator_tickets.json +40 -0
- package/app/backend/scripts/extract_openapi.py +21 -0
- package/app/backend/scripts/seed_demo.py +187 -0
- package/app/backend/setup_demo_review.py +302 -0
- package/app/backend/test_actual_parse.py +41 -0
- package/app/backend/test_agent_streaming.py +61 -0
- package/app/backend/test_parse.py +51 -0
- package/app/backend/test_streaming.py +51 -0
- package/app/backend/test_subprocess_streaming.py +50 -0
- package/app/backend/tests/__init__.py +1 -0
- package/app/backend/tests/conftest.py +46 -0
- package/app/backend/tests/test_auth.py +341 -0
- package/app/backend/tests/test_autonomy_service.py +391 -0
- package/app/backend/tests/test_cleanup_service_safety.py +417 -0
- package/app/backend/tests/test_middleware.py +279 -0
- package/app/backend/tests/test_planner_providers.py +290 -0
- package/app/backend/tests/test_planner_unblock.py +183 -0
- package/app/backend/tests/test_revision_invariants.py +618 -0
- package/app/backend/tests/test_sqlite_kv.py +290 -0
- package/app/backend/tests/test_sqlite_worker.py +353 -0
- package/app/backend/tests/test_task_dispatch.py +100 -0
- package/app/backend/tests/test_ticket_validation.py +304 -0
- package/app/backend/tests/test_udar_agent.py +693 -0
- package/app/backend/tests/test_webhook_service.py +184 -0
- package/app/backend/tickets_output.json +59 -0
- package/app/backend/user_management_tickets.json +50 -0
- package/app/backend/uvicorn.log +0 -0
- package/app/draft.yaml +313 -0
- package/app/frontend/dist/assets/index-LcjCczu5.js +155 -0
- package/app/frontend/dist/assets/index-_FP_279e.css +1 -0
- package/app/frontend/dist/index.html +14 -0
- package/app/frontend/dist/vite.svg +1 -0
- package/app/frontend/package.json +101 -0
- package/bin/cli.js +527 -0
- 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")
|