Skip to content

Commit 170de55

Browse files
committed
Merge pull request 'Fix #2334: wire native PR gate agent configs (pr-reviewer, pr-validator, pr-verifier)' (#2377) from task/2334-native-pr-gate-agents into main
2 parents dd112c3 + fe55508 commit 170de55

4 files changed

Lines changed: 175 additions & 6 deletions

File tree

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
# PR-reviewer agent template
22
#
3-
# Event-dispatched via DispatchTask::ReviewPr.
4-
# The orchestrator builds the bounded native PR gate prompt, owns Gitea
5-
# comment/status posting, and validates the final PrGateResult block.
3+
# Event-dispatched via the native PR gate path (DispatchTask::ReviewPr).
4+
# The orchestrator builds a bounded evidence prompt containing the PR diff,
5+
# changed files, linked issue, and Terraphim concept matches, then delivers
6+
# it to the agent via stdin. The agent produces a structural review report
7+
# and emits one canonical <!-- adf:gate-result {...} --> block.
68
#
7-
# NO schedule field -- this is event-driven, not cron.
9+
# The orchestrator owns all Gitea comment/status posting and gate-result
10+
# parsing. This agent must NOT post comments, update statuses, or attempt
11+
# to read skills outside its working directory.
12+
#
13+
# NO schedule field -- event-driven, not cron.
814
# Subscription-only models only (C1 constraint).
915

1016
[[agents]]
@@ -14,7 +20,6 @@ cli_tool = "/home/alex/.local/bin/claude"
1420
model = "sonnet"
1521
fallback_model = "kimi-for-coding/k2p5"
1622
persona = "Carthos"
17-
skill_chain = ["structural-pr-review"]
1823
max_cpu_seconds = 1800
1924
grace_period_secs = 30
20-
task = "Native structural PR gate producer. Review only the bounded evidence prompt provided by the orchestrator and emit one canonical adf:gate-result block. Do not post comments or statuses."
25+
task = "Native structural PR gate producer. Review only the bounded evidence prompt provided by the orchestrator and emit one canonical adf:gate-result block. Do not call tools, post comments, or update statuses."
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# PR-validator agent template
2+
#
3+
# Event-dispatched via the native PR gate path (DispatchTask::ReviewPr).
4+
# The orchestrator builds a bounded evidence prompt containing the PR diff,
5+
# changed files, linked issue, and acceptance criteria, then delivers it to
6+
# the agent via stdin. The agent validates the change against the linked
7+
# issue, acceptance criteria, user-visible behaviour, and release readiness,
8+
# then emits one canonical <!-- adf:gate-result {...} --> block.
9+
#
10+
# Gate context: adf/validation
11+
# Discipline: requirements validation
12+
#
13+
# The orchestrator owns all Gitea comment/status posting and gate-result
14+
# parsing. This agent must NOT post comments, update statuses, or attempt
15+
# to read skills outside its working directory.
16+
#
17+
# NO schedule field -- event-driven, not cron.
18+
# Subscription-only models only (C1 constraint).
19+
20+
[[agents]]
21+
name = "pr-validator"
22+
layer = "Safety"
23+
cli_tool = "/home/alex/.local/bin/claude"
24+
model = "sonnet"
25+
fallback_model = "kimi-for-coding/k2p5"
26+
persona = "Carthos"
27+
max_cpu_seconds = 1800
28+
grace_period_secs = 30
29+
task = "Native requirements validation PR gate producer. Validate only the bounded evidence prompt provided by the orchestrator and emit one canonical adf:gate-result block. Do not call tools, post comments, or update statuses."
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# PR-verifier agent template
2+
#
3+
# Event-dispatched via the native PR gate path (DispatchTask::ReviewPr).
4+
# The orchestrator builds a bounded evidence prompt containing the PR diff,
5+
# changed files, test evidence, and design intent, then delivers it to the
6+
# agent via stdin. The agent verifies the implementation evidence against
7+
# the design intent and test coverage, then emits one canonical
8+
# <!-- adf:gate-result {...} --> block.
9+
#
10+
# Gate context: adf/verification
11+
# Discipline: design and test verification
12+
#
13+
# The orchestrator owns all Gitea comment/status posting and gate-result
14+
# parsing. This agent must NOT post comments, update statuses, or attempt
15+
# to read skills outside its working directory.
16+
#
17+
# NO schedule field -- event-driven, not cron.
18+
# Subscription-only models only (C1 constraint).
19+
20+
[[agents]]
21+
name = "pr-verifier"
22+
layer = "Safety"
23+
cli_tool = "/home/alex/.local/bin/claude"
24+
model = "sonnet"
25+
fallback_model = "kimi-for-coding/k2p5"
26+
persona = "Carthos"
27+
max_cpu_seconds = 1800
28+
grace_period_secs = 30
29+
task = "Native design and test verification PR gate producer. Verify only the bounded evidence prompt provided by the orchestrator and emit one canonical adf:gate-result block. Do not call tools, post comments, or update statuses."
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
"""Tests for native PR gate agent templates (pr-reviewer, pr-validator, pr-verifier).
2+
3+
Validates Step 5 of .docs/adf/2334/research-design.md:
4+
- TOML parses correctly
5+
- No skill_chain (bounded orchestrator prompt only)
6+
- No schedule (event-driven)
7+
- Task forbids tools, comments, and status posts
8+
- No producer-side shell or gtr usage
9+
"""
10+
11+
from pathlib import Path
12+
13+
try:
14+
import tomllib
15+
except ImportError:
16+
import tomli as tomllib # type: ignore[no-redef]
17+
18+
AGENTS_DIR = Path(__file__).parent.parent / "agents"
19+
20+
GATE_TEMPLATES = {
21+
"pr-reviewer": AGENTS_DIR / "pr-reviewer.toml",
22+
"pr-validator": AGENTS_DIR / "pr-validator.toml",
23+
"pr-verifier": AGENTS_DIR / "pr-verifier.toml",
24+
}
25+
26+
FORBIDDEN_TASK_KEYWORDS = [
27+
"gtr comment",
28+
"curl -",
29+
"source ~/.profile",
30+
"git fetch",
31+
"skill_chain",
32+
"structural-pr-review",
33+
"disciplined-validation",
34+
"disciplined-verification",
35+
]
36+
37+
REQUIRED_FIELDS = [
38+
"name",
39+
"layer",
40+
"cli_tool",
41+
"model",
42+
"fallback_model",
43+
"persona",
44+
"max_cpu_seconds",
45+
"grace_period_secs",
46+
"task",
47+
]
48+
49+
50+
def _load_agent(path: Path) -> dict:
51+
with open(path, "rb") as fh:
52+
config = tomllib.load(fh)
53+
assert "agents" in config and config["agents"], f"No agents in {path}"
54+
return config["agents"][0]
55+
56+
57+
def test_templates_exist():
58+
for name, path in GATE_TEMPLATES.items():
59+
assert path.exists(), f"Missing template for {name}: {path}"
60+
61+
62+
def test_templates_parse():
63+
for path in GATE_TEMPLATES.values():
64+
_load_agent(path)
65+
66+
67+
def test_agent_names_match_files():
68+
for expected_name, path in GATE_TEMPLATES.items():
69+
agent = _load_agent(path)
70+
assert agent["name"] == expected_name
71+
72+
73+
def test_required_fields_present():
74+
for path in GATE_TEMPLATES.values():
75+
agent = _load_agent(path)
76+
for field in REQUIRED_FIELDS:
77+
assert field in agent, f"{path.name} missing field: {field}"
78+
79+
80+
def test_no_skill_chain():
81+
for path in GATE_TEMPLATES.values():
82+
agent = _load_agent(path)
83+
assert "skill_chain" not in agent, f"{path.name} must not define skill_chain"
84+
85+
86+
def test_no_schedule():
87+
for path in GATE_TEMPLATES.values():
88+
agent = _load_agent(path)
89+
assert "schedule" not in agent, f"{path.name} must be event-driven (no schedule)"
90+
91+
92+
def test_task_contract():
93+
for path in GATE_TEMPLATES.values():
94+
agent = _load_agent(path)
95+
task = agent["task"].lower()
96+
assert "adf:gate-result" in task, f"{path.name} task must mention adf:gate-result"
97+
assert "do not call tools" in task, f"{path.name} task must forbid tools"
98+
assert "do not post comments" in task or "do not call tools, post comments" in task
99+
100+
101+
def test_no_producer_side_shell_or_gtr():
102+
for path in GATE_TEMPLATES.values():
103+
agent = _load_agent(path)
104+
task = agent["task"]
105+
for keyword in FORBIDDEN_TASK_KEYWORDS:
106+
assert keyword not in task, f"{path.name} task must not contain: {keyword}"

0 commit comments

Comments
 (0)