docs(prompts): ORCH-092 — аудит 6 агент-промптов (расхардкод, escalation, чистка)
All checks were successful
CI / test (push) Successful in 32s
CI / test (pull_request) Successful in 31s

Эпилог эпика ORCH-52. Docs/prompts-only: src/**, STAGE_TRANSITIONS, QG_CHECKS,
machine-verdict ключи и схема БД не тронуты; frontmatter_validation_strict=False.

- FR-1/FR-2: копируемые frontmatter-примеры всех 6 промптов расхардкожены
  (created_at: <YYYY-MM-DD> / model_used: <resolve ORCH-41> + врезка «не копируй
  буквально, подставь date +%F и модель из конфига»); литерал claude-opus-4-8 —
  только справка в таблице полей.
- FR-3: имена check_* в промптах сверены с QG_CHECKS — несовпадений нет
  (закреплено интеграционным тестом TC-03).
- FR-4: developer «PR>1500 → разбивай» переформулирован в эскалацию на уровне задач.
- FR-5: секция <escalation> у developer/reviewer/tester (после </success_criteria>):
  back-to:analysis / back-to:dev / REQUEST_CHANGES.
- FR-6: deployer — критичные self-hosting-запреты в видной рамке в начале <context>.
- FR-7: tester обогащён worktree-путём, smoke serial_gate (ORCH-088), покрытием TC.
- FR-8: из reviewer удалена мёртвая строка «тот же экземпляр Developer».
- FR-9 (ADR-001 D1): убран ручной git rebase origin/main — свежесть базы держит
  движок (serial-gate ORCH-088 + auto_rebase_onto_main под merge-lease).
- FR-10 (ADR-001 D2): deployer.md оставлен на английском как нормативное исключение.
- FR-11: расширен tests/test_agent_prompts_canon.py (ORCH-092 TC-01…TC-08);
  канон 52d и test_agent_frontmatter_no_model.py зелёные; полный регресс 1278 зелёный.

Документация: 6 промптов, CLAUDE.md, docs/architecture/README.md, CHANGELOG.md.

Refs: ORCH-092

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-09 17:36:48 +03:00
parent 84797e406c
commit 63d862fd34
10 changed files with 291 additions and 33 deletions

View File

@@ -11,6 +11,7 @@ Covers test-plan TC-01..TC-07. TC-08 lives in
regression (TC-10) is the rest of `tests/`.
"""
import os
import re
import pytest
@@ -279,3 +280,156 @@ def test_reviewer_carries_overview_docs_axis():
assert "ORCH-079" in text, (
"reviewer.md does not anchor the overview-docs axis to ORCH-079"
)
# --------------------------------------------------------------------------- #
# ORCH-092 (epilogue of epic ORCH-52): prompt audit of the 6 agents —
# de-hardcode date/model, gate-name parity, escalation sections, dead-line
# removal, tester enrichment, deployer ban-frame. Pure-text checks; only
# TC-03 imports `src/` (the QG_CHECKS registry parity check).
# Covers test-plan TC-01..TC-08 (TC-09/TC-10/TC-11 = existing canon + full regression).
# --------------------------------------------------------------------------- #
def _fenced_blocks(text: str) -> list[str]:
"""Return the body of every ``` fenced code block (the *copyable* examples)."""
blocks: list[str] = []
inside = False
buf: list[str] = []
for line in text.splitlines():
if line.lstrip().startswith("```"):
if inside:
blocks.append("\n".join(buf))
buf = []
inside = not inside
continue
if inside:
buf.append(line)
return blocks
@pytest.mark.parametrize("agent", _AGENTS)
def test_orch092_created_at_is_placeholder_not_literal(agent):
"""TC-01 (AC-1): copyable example uses a date placeholder + a substitution note.
The field name `created_at` stays; only its value becomes a placeholder. No
literal date may survive inside a ``` fenced (copyable) block, else an agent
would copy a stale date verbatim.
"""
text = _read(agent)
assert "created_at: <YYYY-MM-DD>" in text, (
f"{agent}.md does not use the created_at: <YYYY-MM-DD> placeholder"
)
for block in _fenced_blocks(text):
assert re.search(r"created_at:\s*\d", block) is None, (
f"{agent}.md still hardcodes a literal created_at date in a copyable block"
)
assert "date +%F" in text, (
f"{agent}.md does not instruct to substitute the actual date (date +%F)"
)
@pytest.mark.parametrize("agent", _AGENTS)
def test_orch092_model_used_is_placeholder_not_literal(agent):
"""TC-02 (AC-2): copyable example uses a model placeholder, not the literal model.
`model_used: claude-opus-4-8` is allowed as a reference in the field table
(outside the fenced block) but must NOT appear in a copyable example.
"""
text = _read(agent)
assert "model_used: <resolve ORCH-41>" in text, (
f"{agent}.md does not use the model_used: <resolve ORCH-41> placeholder"
)
for block in _fenced_blocks(text):
assert "model_used: claude-opus-4-8" not in block, (
f"{agent}.md still hardcodes model_used: claude-opus-4-8 in a copyable block"
)
def test_orch092_gate_names_match_qg_registry():
"""TC-03 (AC-3): every check_* named in the 6 prompts is a real QG_CHECKS key.
The only test in this module that imports `src/` (integration). Guards against
a prompt naming a non-existent gate; confirms check_tests_passed is valid.
"""
from src.qg.checks import QG_CHECKS
pattern = re.compile(r"check_[a-z_]+")
for agent in _AGENTS:
for name in sorted(set(pattern.findall(_read(agent)))):
assert name in QG_CHECKS, (
f"{agent}.md references gate {name!r} which is absent from QG_CHECKS"
)
assert "check_tests_passed" in QG_CHECKS, "check_tests_passed must remain a real gate"
def test_orch092_developer_pr_oversize_is_escalation_not_split():
"""TC-04 (AC-4): the 'split into smaller PRs' instruction became an escalation."""
text = _read("developer")
assert "разбивай на меньшие PR" not in text, (
"developer.md still carries the unrealisable 'split into smaller PRs' instruction"
)
assert "на уровне задач" in text and "декомпозиц" in text, (
"developer.md does not reframe an oversize PR as task-level decomposition"
)
assert "свой PR" in text, "developer.md lost the 'свой PR' marker"
@pytest.mark.parametrize("agent", ("developer", "reviewer", "tester"))
def test_orch092_escalation_section_present_after_success(agent):
"""TC-05 (AC-5): dev/reviewer/tester carry <escalation> after </success_criteria>."""
text = _read(agent)
# The real section tags sit on their own line (an inline `<escalation>` mention
# in <constraints> uses backticks and must not be mistaken for the section).
open_m = re.search(r"(?m)^<escalation>\s*$", text)
close_m = re.search(r"(?m)^</escalation>\s*$", text)
assert open_m and close_m, f"{agent}.md is missing the <escalation> section"
success_m = re.search(r"(?m)^</success_criteria>\s*$", text)
assert success_m and open_m.start() > success_m.start(), (
f"{agent}.md places <escalation> before </success_criteria> (breaks section order)"
)
def test_orch092_escalation_routes_are_role_specific():
"""TC-05 (AC-5): escalation routes match each role."""
assert "back-to:analysis" in _read("developer"), "developer lacks back-to:analysis route"
assert "back-to:dev" in _read("tester"), "tester lacks back-to:dev route"
assert "REQUEST_CHANGES" in _read("reviewer"), "reviewer lacks REQUEST_CHANGES route"
def test_orch092_tester_enriched():
"""TC-06 (AC-7): tester gains worktree path, serial_gate smoke and TRZ coverage."""
text = _read("tester")
assert "worktree" in text, "tester.md does not mention the task-branch worktree path"
assert "serial_gate" in text, "tester.md /queue smoke omits the serial_gate block check"
assert "04-test-plan.yaml" in text, "tester.md does not require coverage of every TRZ TC"
for marker in _ANTI_REGRESS["tester"]:
assert marker in text, f"tester.md lost anti-regress marker {marker!r}"
def test_orch092_deployer_prominent_ban_frame():
"""TC-07 (AC-6): deployer carries a prominent prod-8500 ban frame inside <context>."""
text = _read("deployer")
context = text[text.index("<context>"):text.index("</context>")]
assert "8500" in context, "deployer.md <context> frame does not name the prod 8500"
assert "NEVER restart the prod" in context, (
"deployer.md does not raise the 'NEVER restart prod 8500' ban into the context frame"
)
for marker in _ANTI_REGRESS["deployer"]:
assert marker in text, f"deployer.md lost anti-regress marker {marker!r}"
def test_orch092_reviewer_dead_line_removed():
"""TC-08 (AC-8): the dead 'same Developer instance' line is gone; live markers stay."""
text = _read("reviewer")
assert "того же экземпляра" not in text, (
"reviewer.md still carries the dead 'same Developer instance' instruction"
)
for marker in (
"REQUEST_CHANGES",
"НЕ обновлена",
"TRACEABILITY.md",
"Известные ограничения",
"ORCH-079",
):
assert marker in text, f"reviewer.md lost live invariant marker {marker!r}"