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,137 @@
|
|
|
1
|
+
"""Pydantic schemas for Review entities (comments and summaries)."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from enum import StrEnum
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AuthorType(StrEnum):
|
|
10
|
+
"""Enum representing the type of author for a review comment."""
|
|
11
|
+
|
|
12
|
+
HUMAN = "human"
|
|
13
|
+
AGENT = "agent"
|
|
14
|
+
SYSTEM = "system"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ReviewDecision(StrEnum):
|
|
18
|
+
"""Enum representing the review decision."""
|
|
19
|
+
|
|
20
|
+
APPROVED = "approved"
|
|
21
|
+
CHANGES_REQUESTED = "changes_requested"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Review Comment Schemas
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ReviewCommentCreate(BaseModel):
|
|
28
|
+
"""Schema for creating a review comment."""
|
|
29
|
+
|
|
30
|
+
file_path: str = Field(..., min_length=1, max_length=500)
|
|
31
|
+
line_number: int = Field(..., ge=1)
|
|
32
|
+
body: str = Field(..., min_length=1)
|
|
33
|
+
author_type: AuthorType = Field(default=AuthorType.HUMAN)
|
|
34
|
+
# Optional: client can provide hunk_header for anchor calculation
|
|
35
|
+
hunk_header: str | None = Field(
|
|
36
|
+
None,
|
|
37
|
+
description="Diff hunk header (e.g., '@@ -10,5 +10,7 @@') for anchor stability",
|
|
38
|
+
)
|
|
39
|
+
line_content: str | None = Field(
|
|
40
|
+
None, description="Content of the line being commented on for anchor stability"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ReviewCommentResponse(BaseModel):
|
|
45
|
+
"""Schema for review comment response."""
|
|
46
|
+
|
|
47
|
+
id: str
|
|
48
|
+
revision_id: str
|
|
49
|
+
file_path: str
|
|
50
|
+
line_number: int
|
|
51
|
+
anchor: str
|
|
52
|
+
body: str
|
|
53
|
+
author_type: AuthorType
|
|
54
|
+
resolved: bool
|
|
55
|
+
created_at: datetime
|
|
56
|
+
line_content: str | None = None
|
|
57
|
+
|
|
58
|
+
model_config = {"from_attributes": True}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ReviewCommentListResponse(BaseModel):
|
|
62
|
+
"""Schema for list of review comments."""
|
|
63
|
+
|
|
64
|
+
comments: list[ReviewCommentResponse]
|
|
65
|
+
total: int
|
|
66
|
+
unresolved_count: int
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# Review Summary Schemas
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class ReviewSubmit(BaseModel):
|
|
73
|
+
"""Schema for submitting a review decision."""
|
|
74
|
+
|
|
75
|
+
decision: ReviewDecision
|
|
76
|
+
summary: str = Field(
|
|
77
|
+
default="", description="High-level review feedback (optional)"
|
|
78
|
+
)
|
|
79
|
+
auto_run_fix: bool = Field(
|
|
80
|
+
default=True,
|
|
81
|
+
description="If changes_requested, automatically trigger new agent execution",
|
|
82
|
+
)
|
|
83
|
+
create_pr: bool = Field(
|
|
84
|
+
default=False,
|
|
85
|
+
description="If approved, create a GitHub PR instead of merging directly to main",
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class ReviewSummaryResponse(BaseModel):
|
|
90
|
+
"""Schema for review summary response."""
|
|
91
|
+
|
|
92
|
+
id: str
|
|
93
|
+
revision_id: str
|
|
94
|
+
decision: ReviewDecision
|
|
95
|
+
body: str
|
|
96
|
+
created_at: datetime
|
|
97
|
+
|
|
98
|
+
model_config = {"from_attributes": True}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# Feedback Bundle Schema
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class FeedbackComment(BaseModel):
|
|
105
|
+
"""Schema for a comment in the feedback bundle."""
|
|
106
|
+
|
|
107
|
+
file_path: str
|
|
108
|
+
line_number: int
|
|
109
|
+
anchor: str
|
|
110
|
+
body: str
|
|
111
|
+
line_content: str | None = Field(
|
|
112
|
+
default=None,
|
|
113
|
+
description="Content of the line being commented on, helps agent locate the issue",
|
|
114
|
+
)
|
|
115
|
+
orphaned: bool = Field(
|
|
116
|
+
default=False,
|
|
117
|
+
description="True if this comment's anchor cannot be found in the current diff",
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class FeedbackBundle(BaseModel):
|
|
122
|
+
"""Schema for the feedback bundle sent to the agent.
|
|
123
|
+
|
|
124
|
+
This is the structured feedback that gets injected into the agent prompt
|
|
125
|
+
when creating a new revision after changes are requested.
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
ticket_id: str
|
|
129
|
+
revision_id: str
|
|
130
|
+
revision_number: int
|
|
131
|
+
decision: str
|
|
132
|
+
summary: str
|
|
133
|
+
comments: list[FeedbackComment]
|
|
134
|
+
orphaned_comment_count: int = Field(
|
|
135
|
+
default=0,
|
|
136
|
+
description="Number of comments that could not be anchored to the current diff",
|
|
137
|
+
)
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Pydantic schemas for Revision entity."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
# Import RevisionStatus from models to avoid duplication
|
|
8
|
+
from app.models.revision import RevisionStatus
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RevisionResponse(BaseModel):
|
|
12
|
+
"""Schema for revision response."""
|
|
13
|
+
|
|
14
|
+
id: str
|
|
15
|
+
ticket_id: str
|
|
16
|
+
job_id: str
|
|
17
|
+
number: int
|
|
18
|
+
status: RevisionStatus
|
|
19
|
+
diff_stat_evidence_id: str | None
|
|
20
|
+
diff_patch_evidence_id: str | None
|
|
21
|
+
created_at: datetime
|
|
22
|
+
unresolved_comment_count: int = Field(
|
|
23
|
+
default=0, description="Number of unresolved review comments"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
model_config = {"from_attributes": True}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class RevisionDetailResponse(RevisionResponse):
|
|
30
|
+
"""Schema for detailed revision response with diff content."""
|
|
31
|
+
|
|
32
|
+
diff_stat: str | None = Field(None, description="Git diff stat output")
|
|
33
|
+
diff_patch: str | None = Field(None, description="Full git diff patch")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class RevisionListResponse(BaseModel):
|
|
37
|
+
"""Schema for list of revisions."""
|
|
38
|
+
|
|
39
|
+
revisions: list[RevisionResponse]
|
|
40
|
+
total: int
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class RevisionDiffResponse(BaseModel):
|
|
44
|
+
"""Schema for revision diff content (both stat and patch - heavyweight)."""
|
|
45
|
+
|
|
46
|
+
revision_id: str
|
|
47
|
+
diff_stat: str | None = Field(None, description="Git diff stat output")
|
|
48
|
+
diff_patch: str | None = Field(None, description="Full git diff patch")
|
|
49
|
+
files: list["DiffFile"] = Field(
|
|
50
|
+
default_factory=list, description="Parsed diff files"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class DiffSummaryResponse(BaseModel):
|
|
55
|
+
"""Schema for lightweight diff summary (stat + file list only).
|
|
56
|
+
|
|
57
|
+
Use this endpoint for initial load - no heavy patch content.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
revision_id: str
|
|
61
|
+
diff_stat: str | None = Field(None, description="Git diff stat output")
|
|
62
|
+
files: list["DiffFile"] = Field(
|
|
63
|
+
default_factory=list, description="Parsed diff files"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class DiffPatchResponse(BaseModel):
|
|
68
|
+
"""Schema for heavyweight diff patch content.
|
|
69
|
+
|
|
70
|
+
Only fetch this when user actually opens the diff viewer.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
revision_id: str
|
|
74
|
+
diff_patch: str | None = Field(None, description="Full git diff patch")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class DiffFile(BaseModel):
|
|
78
|
+
"""Schema for a single file in the diff."""
|
|
79
|
+
|
|
80
|
+
path: str
|
|
81
|
+
old_path: str | None = None
|
|
82
|
+
additions: int = 0
|
|
83
|
+
deletions: int = 0
|
|
84
|
+
status: str = Field(
|
|
85
|
+
default="modified",
|
|
86
|
+
description="File status: added, deleted, modified, renamed",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class TimelineEvent(BaseModel):
|
|
91
|
+
"""Schema for a single event in the revision timeline."""
|
|
92
|
+
|
|
93
|
+
id: str
|
|
94
|
+
event_type: str = Field(
|
|
95
|
+
description="Event type: revision_created, comment_added, review_submitted, job_queued, job_completed"
|
|
96
|
+
)
|
|
97
|
+
actor: str = Field(description="Who triggered this event (human, agent, system)")
|
|
98
|
+
message: str = Field(description="Human-readable description of the event")
|
|
99
|
+
created_at: datetime
|
|
100
|
+
metadata: dict | None = Field(
|
|
101
|
+
default=None, description="Additional event-specific data"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class RevisionTimelineResponse(BaseModel):
|
|
106
|
+
"""Schema for revision timeline (audit trail of events)."""
|
|
107
|
+
|
|
108
|
+
revision_id: str
|
|
109
|
+
events: list[TimelineEvent]
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# For forward reference
|
|
113
|
+
RevisionDiffResponse.model_rebuild()
|
|
114
|
+
DiffSummaryResponse.model_rebuild()
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"""Pydantic schemas for Ticket entity."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
from app.state_machine import ActorType, TicketState
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TicketCreate(BaseModel):
|
|
11
|
+
"""Schema for creating a new ticket."""
|
|
12
|
+
|
|
13
|
+
goal_id: str = Field(..., description="UUID of the parent goal")
|
|
14
|
+
title: str = Field(..., min_length=1, max_length=255)
|
|
15
|
+
description: str | None = None
|
|
16
|
+
priority: int | None = Field(None, ge=0, le=100)
|
|
17
|
+
blocked_by_ticket_id: str | None = Field(
|
|
18
|
+
None,
|
|
19
|
+
description="UUID of a ticket that blocks this one. This ticket cannot be executed until the blocker is DONE.",
|
|
20
|
+
)
|
|
21
|
+
actor_type: ActorType = Field(
|
|
22
|
+
default=ActorType.HUMAN,
|
|
23
|
+
description="Type of actor creating the ticket",
|
|
24
|
+
)
|
|
25
|
+
actor_id: str | None = Field(
|
|
26
|
+
None, description="ID of the actor creating the ticket"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class TicketUpdate(BaseModel):
|
|
31
|
+
"""Schema for updating an existing ticket."""
|
|
32
|
+
|
|
33
|
+
title: str | None = Field(None, min_length=1, max_length=255)
|
|
34
|
+
description: str | None = None
|
|
35
|
+
priority: int | None = Field(None, ge=0, le=100)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class TicketResponse(BaseModel):
|
|
39
|
+
"""Schema for ticket response."""
|
|
40
|
+
|
|
41
|
+
id: str
|
|
42
|
+
goal_id: str
|
|
43
|
+
title: str
|
|
44
|
+
description: str | None
|
|
45
|
+
state: TicketState
|
|
46
|
+
priority: int | None
|
|
47
|
+
sort_order: int | None = Field(
|
|
48
|
+
None, description="Manual sort order within state column"
|
|
49
|
+
)
|
|
50
|
+
blocked_by_ticket_id: str | None = Field(
|
|
51
|
+
None, description="UUID of ticket blocking this one"
|
|
52
|
+
)
|
|
53
|
+
blocked_by_ticket_title: str | None = Field(
|
|
54
|
+
None, description="Title of the ticket blocking this one (if loaded)"
|
|
55
|
+
)
|
|
56
|
+
goal_title: str | None = Field(
|
|
57
|
+
None, description="Title of the parent goal (if loaded)"
|
|
58
|
+
)
|
|
59
|
+
created_at: datetime
|
|
60
|
+
updated_at: datetime
|
|
61
|
+
|
|
62
|
+
model_config = {"from_attributes": True}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class TicketDetailResponse(TicketResponse):
|
|
66
|
+
"""Schema for detailed ticket response with additional context."""
|
|
67
|
+
|
|
68
|
+
goal_title: str | None = None
|
|
69
|
+
goal_description: str | None = None
|
|
70
|
+
priority_label: str | None = None
|
|
71
|
+
state_display: str
|
|
72
|
+
blocked_by_ticket_title: str | None = Field(
|
|
73
|
+
None, description="Title of the ticket blocking this one"
|
|
74
|
+
)
|
|
75
|
+
is_blocked: bool = Field(
|
|
76
|
+
False, description="True if this ticket is blocked by an incomplete dependency"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
@staticmethod
|
|
80
|
+
def get_priority_label(priority: int | None) -> str | None:
|
|
81
|
+
"""Convert numeric priority to human-readable label."""
|
|
82
|
+
if priority is None:
|
|
83
|
+
return None
|
|
84
|
+
if priority >= 80:
|
|
85
|
+
return "High"
|
|
86
|
+
elif priority >= 50:
|
|
87
|
+
return "Medium"
|
|
88
|
+
elif priority >= 20:
|
|
89
|
+
return "Low"
|
|
90
|
+
else:
|
|
91
|
+
return "Very Low"
|
|
92
|
+
|
|
93
|
+
@staticmethod
|
|
94
|
+
def get_state_display(state: TicketState) -> str:
|
|
95
|
+
"""Convert state enum to human-readable display text."""
|
|
96
|
+
display_map = {
|
|
97
|
+
TicketState.PROPOSED: "Proposed",
|
|
98
|
+
TicketState.PLANNED: "Planned",
|
|
99
|
+
TicketState.EXECUTING: "Executing",
|
|
100
|
+
TicketState.VERIFYING: "Verifying",
|
|
101
|
+
TicketState.NEEDS_HUMAN: "Needs Human",
|
|
102
|
+
TicketState.BLOCKED: "Blocked",
|
|
103
|
+
TicketState.DONE: "Done",
|
|
104
|
+
TicketState.ABANDONED: "Abandoned",
|
|
105
|
+
}
|
|
106
|
+
return display_map.get(state, state.value.title())
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class TicketTransition(BaseModel):
|
|
110
|
+
"""Schema for requesting a ticket state transition."""
|
|
111
|
+
|
|
112
|
+
to_state: TicketState = Field(..., description="Target state for the transition")
|
|
113
|
+
actor_type: ActorType = Field(
|
|
114
|
+
..., description="Type of actor performing the transition"
|
|
115
|
+
)
|
|
116
|
+
actor_id: str | None = Field(
|
|
117
|
+
None, description="ID of the actor performing the transition"
|
|
118
|
+
)
|
|
119
|
+
reason: str | None = Field(None, description="Reason for the state transition")
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class TicketWithGoal(TicketResponse):
|
|
123
|
+
"""Schema for ticket response including goal information."""
|
|
124
|
+
|
|
125
|
+
goal_title: str | None = None
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class TicketsByState(BaseModel):
|
|
129
|
+
"""Schema for tickets grouped by state."""
|
|
130
|
+
|
|
131
|
+
state: TicketState
|
|
132
|
+
tickets: list[TicketResponse]
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class BoardResponse(BaseModel):
|
|
136
|
+
"""Schema for the board view - tickets grouped by state."""
|
|
137
|
+
|
|
138
|
+
columns: list[TicketsByState]
|
|
139
|
+
total_tickets: int
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class BulkAcceptRequest(BaseModel):
|
|
143
|
+
"""Schema for bulk accepting proposed tickets."""
|
|
144
|
+
|
|
145
|
+
ticket_ids: list[str] = Field(
|
|
146
|
+
..., min_length=1, description="List of ticket IDs to accept"
|
|
147
|
+
)
|
|
148
|
+
goal_id: str | None = Field(
|
|
149
|
+
None,
|
|
150
|
+
description="If provided, validates all tickets belong to this goal",
|
|
151
|
+
)
|
|
152
|
+
actor_type: ActorType = Field(
|
|
153
|
+
default=ActorType.HUMAN, description="Actor performing the accept"
|
|
154
|
+
)
|
|
155
|
+
actor_id: str | None = Field(None, description="ID of the actor")
|
|
156
|
+
reason: str | None = Field(
|
|
157
|
+
default="Accepted from AI-generated proposal",
|
|
158
|
+
description="Reason for acceptance",
|
|
159
|
+
)
|
|
160
|
+
queue_first: bool = Field(
|
|
161
|
+
default=False,
|
|
162
|
+
description="If true, queue the first accepted ticket for execution",
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class BulkAcceptResult(BaseModel):
|
|
167
|
+
"""Result for a single ticket in bulk accept."""
|
|
168
|
+
|
|
169
|
+
ticket_id: str
|
|
170
|
+
success: bool
|
|
171
|
+
error: str | None = None
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class BulkAcceptResponse(BaseModel):
|
|
175
|
+
"""Response for bulk accept operation."""
|
|
176
|
+
|
|
177
|
+
accepted_ids: list[str] = Field(default_factory=list)
|
|
178
|
+
rejected: list[BulkAcceptResult] = Field(default_factory=list)
|
|
179
|
+
accepted_count: int
|
|
180
|
+
failed_count: int
|
|
181
|
+
queued_job_id: str | None = Field(
|
|
182
|
+
None,
|
|
183
|
+
description="Job ID if queue_first was true and first ticket was queued",
|
|
184
|
+
)
|
|
185
|
+
queued_ticket_id: str | None = Field(
|
|
186
|
+
None,
|
|
187
|
+
description="Ticket ID that was queued (first in request order)",
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class BulkTransitionRequest(BaseModel):
|
|
192
|
+
"""Schema for bulk state transition of multiple tickets."""
|
|
193
|
+
|
|
194
|
+
ticket_ids: list[str] = Field(
|
|
195
|
+
..., min_length=1, description="List of ticket IDs to transition"
|
|
196
|
+
)
|
|
197
|
+
target_state: TicketState = Field(..., description="Target state for all tickets")
|
|
198
|
+
reason: str = Field(
|
|
199
|
+
default="Bulk transition",
|
|
200
|
+
description="Reason for the state transition",
|
|
201
|
+
)
|
|
202
|
+
actor_type: ActorType = Field(
|
|
203
|
+
default=ActorType.HUMAN,
|
|
204
|
+
description="Type of actor performing the transition",
|
|
205
|
+
)
|
|
206
|
+
actor_id: str | None = Field(
|
|
207
|
+
None, description="ID of the actor performing the transition"
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class BulkTransitionResult(BaseModel):
|
|
212
|
+
"""Result for a single ticket in a bulk transition."""
|
|
213
|
+
|
|
214
|
+
ticket_id: str
|
|
215
|
+
success: bool
|
|
216
|
+
error: str | None = None
|
|
217
|
+
from_state: str | None = None
|
|
218
|
+
to_state: str | None = None
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class BulkTransitionResponse(BaseModel):
|
|
222
|
+
"""Response for bulk state transition operation."""
|
|
223
|
+
|
|
224
|
+
results: list[BulkTransitionResult]
|
|
225
|
+
transitioned_count: int
|
|
226
|
+
failed_count: int
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class TicketReorderRequest(BaseModel):
|
|
230
|
+
"""Schema for reordering a ticket within a column."""
|
|
231
|
+
|
|
232
|
+
ticket_id: str = Field(..., description="Ticket ID to reorder")
|
|
233
|
+
new_index: int = Field(
|
|
234
|
+
..., ge=0, description="New position index (0-based) within the column"
|
|
235
|
+
)
|
|
236
|
+
column_state: TicketState = Field(
|
|
237
|
+
..., description="State column in which to reorder"
|
|
238
|
+
)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Pydantic schemas for TicketEvent entity."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, computed_field
|
|
7
|
+
|
|
8
|
+
from app.state_machine import ActorType, EventType, TicketState
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TicketEventResponse(BaseModel):
|
|
12
|
+
"""Schema for ticket event response."""
|
|
13
|
+
|
|
14
|
+
id: str
|
|
15
|
+
ticket_id: str
|
|
16
|
+
event_type: EventType
|
|
17
|
+
from_state: TicketState | None
|
|
18
|
+
to_state: TicketState | None
|
|
19
|
+
actor_type: ActorType
|
|
20
|
+
actor_id: str | None
|
|
21
|
+
reason: str | None
|
|
22
|
+
payload: dict[str, Any] | None
|
|
23
|
+
created_at: datetime
|
|
24
|
+
|
|
25
|
+
model_config = {"from_attributes": True}
|
|
26
|
+
|
|
27
|
+
@computed_field
|
|
28
|
+
@property
|
|
29
|
+
def event_type_display(self) -> str:
|
|
30
|
+
"""Human-readable event type."""
|
|
31
|
+
display_map = {
|
|
32
|
+
EventType.CREATED: "Created",
|
|
33
|
+
EventType.TRANSITIONED: "Transitioned",
|
|
34
|
+
EventType.UPDATED: "Updated",
|
|
35
|
+
EventType.COMMENT: "Comment",
|
|
36
|
+
}
|
|
37
|
+
return display_map.get(self.event_type, self.event_type.value.title())
|
|
38
|
+
|
|
39
|
+
@computed_field
|
|
40
|
+
@property
|
|
41
|
+
def actor_type_display(self) -> str:
|
|
42
|
+
"""Human-readable actor type."""
|
|
43
|
+
display_map = {
|
|
44
|
+
ActorType.HUMAN: "Human",
|
|
45
|
+
ActorType.PLANNER: "AI Planner",
|
|
46
|
+
ActorType.EXECUTOR: "AI Executor",
|
|
47
|
+
ActorType.SYSTEM: "System",
|
|
48
|
+
}
|
|
49
|
+
return display_map.get(self.actor_type, self.actor_type.value.title())
|
|
50
|
+
|
|
51
|
+
@computed_field
|
|
52
|
+
@property
|
|
53
|
+
def from_state_display(self) -> str | None:
|
|
54
|
+
"""Human-readable from state."""
|
|
55
|
+
if self.from_state is None:
|
|
56
|
+
return None
|
|
57
|
+
return self.from_state.value.replace("_", " ").title()
|
|
58
|
+
|
|
59
|
+
@computed_field
|
|
60
|
+
@property
|
|
61
|
+
def to_state_display(self) -> str | None:
|
|
62
|
+
"""Human-readable to state."""
|
|
63
|
+
if self.to_state is None:
|
|
64
|
+
return None
|
|
65
|
+
return self.to_state.value.replace("_", " ").title()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class TicketEventListResponse(BaseModel):
|
|
69
|
+
"""Schema for list of ticket events response."""
|
|
70
|
+
|
|
71
|
+
events: list[TicketEventResponse]
|
|
72
|
+
total: int
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Pydantic schemas for Workspace API responses."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class WorkspaceResponse(BaseModel):
|
|
9
|
+
"""Response model for workspace information."""
|
|
10
|
+
|
|
11
|
+
model_config = ConfigDict(from_attributes=True)
|
|
12
|
+
|
|
13
|
+
id: str
|
|
14
|
+
ticket_id: str
|
|
15
|
+
worktree_path: str
|
|
16
|
+
branch_name: str
|
|
17
|
+
created_at: datetime
|
|
18
|
+
cleaned_up_at: datetime | None = None
|
|
19
|
+
is_active: bool
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Service layer for Draft business logic."""
|
|
2
|
+
|
|
3
|
+
from app.services.board_service import BoardService
|
|
4
|
+
from app.services.cleanup_service import CleanupService
|
|
5
|
+
from app.services.context_gatherer import ContextGatherer
|
|
6
|
+
from app.services.goal_service import GoalService
|
|
7
|
+
from app.services.job_service import JobService
|
|
8
|
+
from app.services.llm_service import LLMService
|
|
9
|
+
from app.services.merge_service import MergeService
|
|
10
|
+
from app.services.planner_service import PlannerService
|
|
11
|
+
from app.services.review_service import ReviewService
|
|
12
|
+
from app.services.revision_service import RevisionService
|
|
13
|
+
from app.services.ticket_generation_service import TicketGenerationService
|
|
14
|
+
from app.services.ticket_service import TicketService
|
|
15
|
+
from app.services.workspace_service import WorkspaceService
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"BoardService",
|
|
19
|
+
"CleanupService",
|
|
20
|
+
"ContextGatherer",
|
|
21
|
+
"GoalService",
|
|
22
|
+
"JobService",
|
|
23
|
+
"LLMService",
|
|
24
|
+
"MergeService",
|
|
25
|
+
"PlannerService",
|
|
26
|
+
"ReviewService",
|
|
27
|
+
"RevisionService",
|
|
28
|
+
"TicketGenerationService",
|
|
29
|
+
"TicketService",
|
|
30
|
+
"WorkspaceService",
|
|
31
|
+
]
|