feat(gateway): run the full fast gate (incl. complexity) at the dev's desk

Add a per-project `quality_command` (model + ORM + API + panel + migration 029)
that the pre-submit gate prefers over the lint/typecheck pair. Pointed at the new
`make gate` target (ruff format --check + ruff check + mypy + xenon, no tests),
i_am_done now catches lint, type AND complexity failures in the developer's
workspace before QA — closing the gap where over-complex code only failed in CI.
Falls back to lint+typecheck when unset; a no-op when no commands are configured.
This commit is contained in:
Renn F
2026-06-14 05:24:09 +02:00
parent 481c5fe17b
commit a8b387b5bb
12 changed files with 123 additions and 4 deletions
+4
View File
@@ -42,6 +42,7 @@ class ProjectResponse(BaseModel):
format_command: str | None = None
typecheck_command: str | None = None
build_command: str | None = None
quality_command: str | None = None
# Runtime state
workspace_path: str | None = None
@@ -103,6 +104,7 @@ class ProjectCreateRequest(BaseModel):
format_command: str | None = None
typecheck_command: str | None = None
build_command: str | None = None
quality_command: str | None = None
class ProjectUpdateRequest(BaseModel):
@@ -126,6 +128,7 @@ class ProjectUpdateRequest(BaseModel):
format_command: str | None = None
typecheck_command: str | None = None
build_command: str | None = None
quality_command: str | None = None
# State
is_active: bool | None = None
@@ -165,6 +168,7 @@ def project_to_response(project: "ProjectTable") -> ProjectResponse:
format_command=project.format_command,
typecheck_command=project.typecheck_command,
build_command=project.build_command,
quality_command=project.quality_command,
workspace_path=project.workspace_path,
last_synced_at=project.last_synced_at,
head_commit=project.head_commit,
+4
View File
@@ -448,6 +448,10 @@ class ProjectTable(Base):
format_command: Mapped[str | None] = mapped_column(String(500), nullable=True)
typecheck_command: Mapped[str | None] = mapped_column(String(500), nullable=True)
build_command: Mapped[str | None] = mapped_column(String(500), nullable=True)
# Fast pre-submit gate command (lint+types+complexity, no tests). When set,
# the agent i_am_done gate runs this in the dev's workspace instead of the
# lint/typecheck pair — e.g. "make gate".
quality_command: Mapped[str | None] = mapped_column(String(500), nullable=True)
# Access Control
assigned_cell: Mapped[Team] = mapped_column(_str_enum(Team), nullable=False)
+10
View File
@@ -70,6 +70,14 @@ class Project(TimestampMixin):
build_command: str | None = Field(
default=None, description="Command to build (e.g., 'pnpm build')"
)
quality_command: str | None = Field(
default=None,
description=(
"Fast pre-submit gate command run in the dev's workspace at "
"i_am_done (lint+types+complexity, no tests; e.g. 'make gate'). "
"When set it replaces the lint/typecheck pair in the gate."
),
)
# Access Control
assigned_cell: Team = Field(..., description="Which cell owns this project")
@@ -120,6 +128,7 @@ class ProjectCreate(RobocoBase):
format_command: str | None = None
typecheck_command: str | None = None
build_command: str | None = None
quality_command: str | None = None
class ProjectUpdate(RobocoBase):
@@ -141,6 +150,7 @@ class ProjectUpdate(RobocoBase):
format_command: str | None = None
typecheck_command: str | None = None
build_command: str | None = None
quality_command: str | None = None
assigned_cell: Team | None = None
allowed_agents: list[UUID] | None = None
is_active: bool | None = None
+9 -3
View File
@@ -1983,11 +1983,17 @@ class GitService(BaseService):
@staticmethod
def _fast_gate_commands(project: Any) -> list[tuple[str, str]]:
"""The project's non-mutating fast-gate commands (lint + typecheck).
"""The project's non-mutating fast-gate commands.
Format and the test suite are intentionally excluded: format mutates
files, and the slow test run stays on CI.
A configured ``quality_command`` is the project's complete fast gate
(lint + types + complexity, no tests; e.g. ``make gate``) and takes
precedence it runs alone. Otherwise fall back to the lint + typecheck
pair. Format and the test suite are intentionally excluded: format
mutates files, and the slow test run stays on CI.
"""
quality = getattr(project, "quality_command", None)
if quality:
return [("quality", quality)]
candidates = (
("lint", getattr(project, "lint_command", None)),
("typecheck", getattr(project, "typecheck_command", None)),
+1
View File
@@ -103,6 +103,7 @@ class ProjectService(BaseService):
format_command=data.format_command,
typecheck_command=data.typecheck_command,
build_command=data.build_command,
quality_command=data.quality_command,
created_by=created_by,
)