From 20747c2b655d6c4fdc7c41e61c5aa935f5a65b87 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Sun, 25 Oct 2026 22:28:19 +0400 Subject: [PATCH] =?UTF-8?q?=D0=90=D0=B3=D0=B5=D0=BD=D1=82=20v16:=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=B7=D0=BE=D1=80=20=D0=BC=D0=B5=D0=B6=D0=B4=D1=83=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=BF=D0=B8=D1=81=D1=8F=D0=BC=D0=B8=20=D0=B8=20?= =?UTF-8?q?=D1=81=D0=B2=D0=B5=D1=80=D0=BA=D0=B0=20=D0=BF=D0=BE=D0=BB=D1=8F?= =?UTF-8?q?=20=D0=BE=D1=86=D0=B5=D0=BD=D0=BA=D0=B8=20=D0=B4=D0=BE=D1=81?= =?UTF-8?q?=D0=BA=D0=B8=20(#53,=20I-1,=20I-2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I-1: если ensure_fields записал поля через PUT /issue, перед записью оценки — зазор ensure_fields. I-2: fieldId оценки доски сверяется с field_ids.story_points; расхождение — failed без записи. Co-Authored-By: Claude Opus 5.5 --- .../changes/agent-v16-sp-estimation/design.md | 2 ++ src/agent.py | 9 +++++++- tests/test_executor_fields.py | 21 +++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/openspec/changes/agent-v16-sp-estimation/design.md b/openspec/changes/agent-v16-sp-estimation/design.md index 33abe4c..7e4c336 100644 --- a/openspec/changes/agent-v16-sp-estimation/design.md +++ b/openspec/changes/agent-v16-sp-estimation/design.md @@ -32,3 +32,5 @@ the appropriate screen, or unknown». Поле оценки не на экран | 25.10.2026 | R-2: сначала остальные поля, затем оценка | новый путь не должен ломать остальные поля | частичная запись, повтор безопасен | | 25.10.2026 | R-3: без `GET /issue`, если в команде только оценка | минус вызов Jira на команду в пакете оценок | нет | | 25.10.2026 | R-5: `value` — число как есть | Jira отдаёт число; живая проверка до пакета | одна правка агента | +| 25.10.2026 | I-1 (финальное ревью): между `PUT /issue` и `PUT …/estimation` одной команды — зазор `ensure_fields` | политика темпа: зазор между записями внутри команды | пакет «оценка + поля» идёт медленнее | +| 25.10.2026 | I-2 (финальное ревью): `fieldId` оценки доски сверяется с `field_ids.story_points` (если задан), расхождение — `failed` без записи | иначе число уйдёт в чужое поле доски другого проекта | лишний отказ, если поле настроено неверно — правится настройкой | diff --git a/src/agent.py b/src/agent.py index 919c6a4..942a53a 100644 --- a/src/agent.py +++ b/src/agent.py @@ -890,7 +890,14 @@ class Executor: changed = self.ensure_issue_fields(key, p, sp_fid) if by_board: path = AGILE + "/issue/%s/estimation" % key - if not _same_points(self.jira.get(path, boardId=p["board_id"]).get("value"), p["story_points"]): + est = self.jira.get(path, boardId=p["board_id"]) + own = (self.s.get("field_ids") or {}).get("story_points") + if own and est.get("fieldId") and est["fieldId"] != own: # доска оценивает другим полем — не писать туда + return self._out("failed", error="у доски %s оценка — поле %s, а не story points (%s)" + % (p["board_id"], est["fieldId"], own)) + if not _same_points(est.get("value"), p["story_points"]): + if changed: # вторая запись команды — по зазору типа (политика темпа) + self.gap("ensure_fields") self.jira.call("PUT", path, {"value": p["story_points"]}, params={"boardId": p["board_id"]}) changed.append("story_points") return self._out("done", {"changed": changed}) diff --git a/tests/test_executor_fields.py b/tests/test_executor_fields.py index 20bdf5b..12f98c2 100644 --- a/tests/test_executor_fields.py +++ b/tests/test_executor_fields.py @@ -352,5 +352,26 @@ class ExecutorFieldsTest(unittest.TestCase): self.assertEqual((out["status"], out["error"]), ("failed", "story_points — число ≥ 0 или null")) self.assertEqual(self.fake.requests, []) + def test_points_by_board_gap_after_fields(self): # I-1: зазор ensure_fields между двумя записями + gaps = [] + self.ex.sleep = gaps.append + self.ex.s["pacing"] = {"ensure_fields": 120} + self.points("AURORA-1", None) + self.run_cmd("ensure_fields", key="AURORA-1", duedate="2026-10-30", story_points=3, board_id=4216) + self.assertEqual(gaps, [120]) + self.run_cmd("ensure_fields", key="AURORA-1", duedate="2026-10-30", story_points=5, board_id=4216) + self.assertEqual(gaps, [120]) # PUT /issue не было — паузы нет + + def test_points_by_board_other_field(self): # I-2: оценка доски — не поле story points + self.points("AURORA-1", 1.0) + self.ex.s["field_ids"] = dict(SETTINGS["field_ids"], story_points="customfield_20000") + out = self.run_cmd("ensure_fields", key="AURORA-1", story_points=2, board_id=4216) + self.assertEqual((out["status"], out["error"]), + ("failed", "у доски 4216 оценка — поле customfield_10106, а не story points (customfield_20000)")) + self.assertEqual(self.fake.estimation_bodies, []) + self.ex.s["field_ids"] = dict(SETTINGS["field_ids"], story_points="customfield_10106") + out = self.run_cmd("ensure_fields", key="AURORA-1", story_points=2, board_id=4216) + self.assertEqual(out["result"], {"changed": ["story_points"]}) + if __name__ == "__main__": unittest.main()