Coder preparations

This commit is contained in:
2026-09-22 23:21:59 +03:00
parent 3d1df7fee5
commit 1f2dc25e0b
32 changed files with 3205 additions and 26 deletions
+89
View File
@@ -0,0 +1,89 @@
from typing import Literal
from pydantic import Field, model_validator
from contracts.base import _StrictModel
class LanguageSnapshot(_StrictModel):
"""
The language the plan commits to, plus the toolchain it will use.
Chosen by Plan from the target files; consumed by Act and Verify.
"""
name: str = Field(
min_length=1,
description="Language name, e.g. 'python', 'typescript', 'go'.",
)
version: str | None = Field(
description="Language version if known, else null.",
)
test_runner: str = Field(
min_length=1,
description="How tests are run, e.g. 'pytest', 'vitest', 'go test'.",
)
formatter: str | None = Field(
description="Formatter command, or null if none.",
)
linter: str | None = Field(
description="Linter command, or null if none.",
)
detected_from: list[str] = Field(
description="Marker files used to detect, e.g. ['pyproject.toml'].",
)
class PlanStep(_StrictModel):
order: int = Field(ge=1, description="1-based ordering of this step.")
file: str = Field(
min_length=1,
description="Repo-relative path (no leading '/', no '..').",
)
change_kind: Literal["create", "modify", "delete"]
description: str = Field(
min_length=1,
description="What changes in this file and why, 1–3 sentences.",
)
class PlanOutput(_StrictModel):
summary: str = Field(
min_length=1, description="1–3 sentence summary of the whole change."
)
steps: list[PlanStep] = Field(
min_length=1, description="Ordered steps. Each step is one file."
)
target_files: list[str] = Field(
min_length=1, description="Unique repo-relative files this plan touches."
)
target_tests: list[str] = Field(
description="Tests that must pass. May be empty for non-code changes."
)
language: LanguageSnapshot
breaks_existing: bool = Field(
description="True if this changes existing behavior a user or caller "
"could rely on.",
)
risk_notes: list[str] = Field(
description="Reasons this might be risky. Non-empty if breaks_existing.",
)
@model_validator(mode="after")
def _consistency(self) -> "PlanOutput":
# 1. target_files must equal the set of files in steps.
step_files = {s.file for s in self.steps}
if set(self.target_files) != step_files:
raise ValueError(
f"target_files {sorted(self.target_files)} must equal "
f"the set of step files {sorted(step_files)}"
)
# 2. breaks_existing implies risk_notes non-empty.
if self.breaks_existing and not self.risk_notes:
raise ValueError("breaks_existing=True requires at least one risk note")
# 3. No absolute paths, no traversal.
for f in self.target_files:
if f.startswith("/") or ".." in f.split("/"):
raise ValueError(f"unsafe path: {f!r}")
return self