GapDetector

This commit is contained in:
2026-09-26 12:50:19 +03:00
parent 7f15d0e0ac
commit a136451568
12 changed files with 1209 additions and 272 deletions
+7
View File
@@ -22,3 +22,10 @@ LanguageLiteral = Literal[
Language.CPP,
Language.C,
]
def _normalize_language(raw: dict) -> dict:
lang = raw.get("language")
if isinstance(lang, dict) and isinstance(lang.get("name"), str):
lang["name"] = lang["name"].strip().lower()
return raw
+19 -23
View File
@@ -2,34 +2,19 @@ from typing import Literal
from pydantic import Field, model_validator
from contracts.CodeLanguages import Language
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'.",
)
name: Language = Field(description="Primary language the plan commits to.")
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'].",
description="Language version if known (e.g. '3.12'), else null.",
)
test_runner: str = Field(min_length=1)
formatter: str | None = Field(description="Formatter command, or null.")
linter: str | None = Field(description="Linter command, or null.")
detected_from: list[str] = Field(min_length=1)
class PlanStep(_StrictModel):
@@ -86,4 +71,15 @@ class PlanOutput(_StrictModel):
if f.startswith("/") or ".." in f.split("/"):
raise ValueError(f"unsafe path: {f!r}")
return self
return self
def wrap_ref_siblings(node):
if isinstance(node, dict):
if "$ref" in node and len(node) > 1:
ref = node["$ref"]
siblings = {k: wrap_ref_siblings(v) for k, v in node.items() if k != "$ref"}
return {"anyOf": [{"$ref": ref}], **siblings}
return {k: wrap_ref_siblings(v) for k, v in node.items()}
if isinstance(node, list):
return [wrap_ref_siblings(x) for x in node]
return node