diamond-dev 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- diamond_dev/__init__.py +1 -0
- diamond_dev/acceptance.py +79 -0
- diamond_dev/agents.py +186 -0
- diamond_dev/commands.py +294 -0
- diamond_dev/comparison_bundle.py +345 -0
- diamond_dev/config.py +793 -0
- diamond_dev/config_init.py +266 -0
- diamond_dev/errors.py +41 -0
- diamond_dev/executor.py +382 -0
- diamond_dev/git_ops.py +423 -0
- diamond_dev/logging_setup.py +209 -0
- diamond_dev/main.py +83 -0
- diamond_dev/markdown.py +10 -0
- diamond_dev/naming.py +123 -0
- diamond_dev/notify.py +46 -0
- diamond_dev/orchestrator.py +1169 -0
- diamond_dev/orchestrator_repositories.py +245 -0
- diamond_dev/pr.py +160 -0
- diamond_dev/preflight.py +68 -0
- diamond_dev/providers.py +131 -0
- diamond_dev/report.py +236 -0
- diamond_dev/review_judgments.py +239 -0
- diamond_dev/workflow.py +283 -0
- diamond_dev-0.1.0.dist-info/METADATA +518 -0
- diamond_dev-0.1.0.dist-info/RECORD +28 -0
- diamond_dev-0.1.0.dist-info/WHEEL +4 -0
- diamond_dev-0.1.0.dist-info/entry_points.txt +3 -0
- diamond_dev-0.1.0.dist-info/licenses/LICENSE +201 -0
diamond_dev/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Diamond Dev application package."""
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Acceptance marker helpers for wiki comparison pages."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from collections.abc import Sequence
|
|
7
|
+
from typing import Final
|
|
8
|
+
|
|
9
|
+
from diamond_dev.errors import MalformedAcceptanceError
|
|
10
|
+
|
|
11
|
+
DEFAULT_ACCEPTANCE_AGENTS: Final = ("codex", "claude")
|
|
12
|
+
ACCEPTANCE_CHECKBOX: Final = "- [ ] Accept: (codex/claude)"
|
|
13
|
+
_ACCEPTANCE_LINE_PATTERN: Final = re.compile(r"^- \[[ xX]\] Accept:")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def append_acceptance_checkbox(
|
|
17
|
+
markdown: str,
|
|
18
|
+
agent_names: Sequence[str] = DEFAULT_ACCEPTANCE_AGENTS,
|
|
19
|
+
) -> str:
|
|
20
|
+
"""Append the deterministic acceptance checkbox to markdown content."""
|
|
21
|
+
separator = "" if markdown.endswith("\n") else "\n"
|
|
22
|
+
return f"{markdown}{separator}{acceptance_checkbox(agent_names)}\n"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def ensure_acceptance_checkbox(
|
|
26
|
+
markdown: str,
|
|
27
|
+
agent_names: Sequence[str] = DEFAULT_ACCEPTANCE_AGENTS,
|
|
28
|
+
) -> str:
|
|
29
|
+
"""Return markdown with exactly one valid acceptance marker."""
|
|
30
|
+
acceptance_lines = _acceptance_lines(markdown)
|
|
31
|
+
if acceptance_lines:
|
|
32
|
+
parse_acceptance(markdown, agent_names)
|
|
33
|
+
return markdown
|
|
34
|
+
return append_acceptance_checkbox(markdown, agent_names)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def parse_acceptance(
|
|
38
|
+
markdown: str,
|
|
39
|
+
agent_names: Sequence[str] = DEFAULT_ACCEPTANCE_AGENTS,
|
|
40
|
+
) -> str | None:
|
|
41
|
+
"""Parse the comparison acceptance marker."""
|
|
42
|
+
allowed_agents = tuple(agent_names)
|
|
43
|
+
acceptance_lines = _acceptance_lines(markdown)
|
|
44
|
+
if not acceptance_lines:
|
|
45
|
+
return None
|
|
46
|
+
if len(acceptance_lines) > 1:
|
|
47
|
+
raise MalformedAcceptanceError("Comparison file has multiple accept markers")
|
|
48
|
+
|
|
49
|
+
line = acceptance_lines[0]
|
|
50
|
+
if line == acceptance_checkbox(allowed_agents):
|
|
51
|
+
return None
|
|
52
|
+
normalized_line = line.replace("[X]", "[x]", 1)
|
|
53
|
+
for agent_name in allowed_agents:
|
|
54
|
+
if normalized_line == accepted_line(agent_name):
|
|
55
|
+
return agent_name
|
|
56
|
+
raise MalformedAcceptanceError(f"Invalid acceptance marker: {line}")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def acceptance_checkbox(agent_names: Sequence[str]) -> str:
|
|
60
|
+
"""Return the unchecked acceptance marker for allowed agents."""
|
|
61
|
+
return f"- [ ] Accept: ({'/'.join(agent_names)})"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def accepted_line(agent_name: str) -> str:
|
|
65
|
+
"""Return the checked acceptance marker for one agent."""
|
|
66
|
+
return f"- [x] Accept: {agent_name}"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def acceptance_wait_delays() -> tuple[int, ...]:
|
|
70
|
+
"""Return acceptance polling waits in seconds."""
|
|
71
|
+
return (120, *(minutes * 60 for minutes in range(3, 13)))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _acceptance_lines(markdown: str) -> list[str]:
|
|
75
|
+
return [
|
|
76
|
+
stripped_line
|
|
77
|
+
for line in markdown.splitlines()
|
|
78
|
+
if _ACCEPTANCE_LINE_PATTERN.match(stripped_line := line.strip())
|
|
79
|
+
]
|
diamond_dev/agents.py
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""Built-in agent adapter registry for Diamond Dev."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable, Mapping
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Literal
|
|
9
|
+
|
|
10
|
+
from diamond_dev.commands import (
|
|
11
|
+
build_claude_interactive_review_command,
|
|
12
|
+
build_claude_print_command,
|
|
13
|
+
build_coderabbit_review_command,
|
|
14
|
+
build_codex_command,
|
|
15
|
+
build_gemini_command,
|
|
16
|
+
)
|
|
17
|
+
from diamond_dev.errors import DiamondDevError
|
|
18
|
+
|
|
19
|
+
type AgentCapability = Literal[
|
|
20
|
+
"implementation",
|
|
21
|
+
"comparison_judge",
|
|
22
|
+
"comparison_fixer",
|
|
23
|
+
"review_provider",
|
|
24
|
+
"review_judge",
|
|
25
|
+
"review_fixer",
|
|
26
|
+
"final_reviewer",
|
|
27
|
+
]
|
|
28
|
+
type PromptCommandBuilder = Callable[[Path, str, str | None], tuple[str, ...]]
|
|
29
|
+
type ReviewCommandBuilder = Callable[[str, str | None], tuple[str, ...]]
|
|
30
|
+
type InteractiveReviewCommandBuilder = Callable[[str, str | None], tuple[str, ...]]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True, slots=True)
|
|
34
|
+
class AgentAdapter:
|
|
35
|
+
"""Command adapter and capability metadata for one built-in agent CLI."""
|
|
36
|
+
|
|
37
|
+
name: str
|
|
38
|
+
executable: str
|
|
39
|
+
capabilities: frozenset[AgentCapability]
|
|
40
|
+
build_prompt_command: PromptCommandBuilder | None = None
|
|
41
|
+
build_review_command: ReviewCommandBuilder | None = None
|
|
42
|
+
build_interactive_review_command: InteractiveReviewCommandBuilder | None = None
|
|
43
|
+
|
|
44
|
+
def has_capability(self, capability: AgentCapability) -> bool:
|
|
45
|
+
"""Return whether this adapter can fill a workflow role."""
|
|
46
|
+
return capability in self.capabilities
|
|
47
|
+
|
|
48
|
+
def require_capability(self, capability: AgentCapability) -> None:
|
|
49
|
+
"""Raise when this adapter cannot fill a workflow role."""
|
|
50
|
+
if not self.has_capability(capability):
|
|
51
|
+
raise DiamondDevError(
|
|
52
|
+
f"Agent adapter `{self.name}` does not support `{capability}`",
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def prompt_command(
|
|
56
|
+
self,
|
|
57
|
+
repo_dir: Path,
|
|
58
|
+
prompt: str,
|
|
59
|
+
*,
|
|
60
|
+
model: str | None,
|
|
61
|
+
capability: AgentCapability,
|
|
62
|
+
) -> tuple[str, ...]:
|
|
63
|
+
"""Build a prompt-driven command for a capable adapter."""
|
|
64
|
+
self.require_capability(capability)
|
|
65
|
+
if self.build_prompt_command is None:
|
|
66
|
+
raise DiamondDevError(
|
|
67
|
+
f"Agent adapter `{self.name}` cannot build prompt commands",
|
|
68
|
+
)
|
|
69
|
+
return self.build_prompt_command(repo_dir, prompt, model)
|
|
70
|
+
|
|
71
|
+
def review_command(
|
|
72
|
+
self,
|
|
73
|
+
base_branch: str,
|
|
74
|
+
*,
|
|
75
|
+
model: str | None,
|
|
76
|
+
) -> tuple[str, ...]:
|
|
77
|
+
"""Build a review-provider command."""
|
|
78
|
+
self.require_capability("review_provider")
|
|
79
|
+
if self.build_review_command is None:
|
|
80
|
+
raise DiamondDevError(
|
|
81
|
+
f"Agent adapter `{self.name}` cannot build review commands",
|
|
82
|
+
)
|
|
83
|
+
return self.build_review_command(base_branch, model)
|
|
84
|
+
|
|
85
|
+
def interactive_review_command(
|
|
86
|
+
self,
|
|
87
|
+
pr_number: str,
|
|
88
|
+
*,
|
|
89
|
+
model: str | None,
|
|
90
|
+
) -> tuple[str, ...]:
|
|
91
|
+
"""Build an interactive final-review command."""
|
|
92
|
+
self.require_capability("final_reviewer")
|
|
93
|
+
if self.build_interactive_review_command is None:
|
|
94
|
+
raise DiamondDevError(
|
|
95
|
+
f"Agent adapter `{self.name}` cannot build interactive review commands",
|
|
96
|
+
)
|
|
97
|
+
return self.build_interactive_review_command(pr_number, model)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _codex_prompt_command(
|
|
101
|
+
repo_dir: Path,
|
|
102
|
+
prompt: str,
|
|
103
|
+
model: str | None,
|
|
104
|
+
) -> tuple[str, ...]:
|
|
105
|
+
return build_codex_command(repo_dir, prompt, model=model)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _claude_prompt_command(
|
|
109
|
+
_repo_dir: Path,
|
|
110
|
+
prompt: str,
|
|
111
|
+
model: str | None,
|
|
112
|
+
) -> tuple[str, ...]:
|
|
113
|
+
return build_claude_print_command(prompt, model=model)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _gemini_prompt_command(
|
|
117
|
+
_repo_dir: Path,
|
|
118
|
+
prompt: str,
|
|
119
|
+
model: str | None,
|
|
120
|
+
) -> tuple[str, ...]:
|
|
121
|
+
return build_gemini_command(prompt, model=model)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _coderabbit_review_command(
|
|
125
|
+
base_branch: str,
|
|
126
|
+
_model: str | None,
|
|
127
|
+
) -> tuple[str, ...]:
|
|
128
|
+
return build_coderabbit_review_command(base_branch)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _claude_interactive_review_command(
|
|
132
|
+
pr_number: str,
|
|
133
|
+
model: str | None,
|
|
134
|
+
) -> tuple[str, ...]:
|
|
135
|
+
return build_claude_interactive_review_command(pr_number, model=model)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
_CODE_WRITER_CAPABILITIES: frozenset[AgentCapability] = frozenset(
|
|
139
|
+
{
|
|
140
|
+
"implementation",
|
|
141
|
+
"comparison_fixer",
|
|
142
|
+
"review_judge",
|
|
143
|
+
"review_fixer",
|
|
144
|
+
},
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
BUILTIN_AGENT_ADAPTERS: Mapping[str, AgentAdapter] = {
|
|
148
|
+
"codex": AgentAdapter(
|
|
149
|
+
name="codex",
|
|
150
|
+
executable="codex",
|
|
151
|
+
capabilities=_CODE_WRITER_CAPABILITIES,
|
|
152
|
+
build_prompt_command=_codex_prompt_command,
|
|
153
|
+
),
|
|
154
|
+
"claude": AgentAdapter(
|
|
155
|
+
name="claude",
|
|
156
|
+
executable="claude",
|
|
157
|
+
capabilities=frozenset({*_CODE_WRITER_CAPABILITIES, "final_reviewer"}),
|
|
158
|
+
build_prompt_command=_claude_prompt_command,
|
|
159
|
+
build_interactive_review_command=_claude_interactive_review_command,
|
|
160
|
+
),
|
|
161
|
+
"gemini": AgentAdapter(
|
|
162
|
+
name="gemini",
|
|
163
|
+
executable="gemini",
|
|
164
|
+
capabilities=frozenset({"comparison_judge"}),
|
|
165
|
+
build_prompt_command=_gemini_prompt_command,
|
|
166
|
+
),
|
|
167
|
+
"coderabbit": AgentAdapter(
|
|
168
|
+
name="coderabbit",
|
|
169
|
+
executable="coderabbit",
|
|
170
|
+
capabilities=frozenset({"review_provider"}),
|
|
171
|
+
build_review_command=_coderabbit_review_command,
|
|
172
|
+
),
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def adapter_names() -> frozenset[str]:
|
|
177
|
+
"""Return names of built-in adapters."""
|
|
178
|
+
return frozenset(BUILTIN_AGENT_ADAPTERS)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def resolve_adapter(adapter_name: str) -> AgentAdapter:
|
|
182
|
+
"""Return a built-in adapter by name."""
|
|
183
|
+
try:
|
|
184
|
+
return BUILTIN_AGENT_ADAPTERS[adapter_name]
|
|
185
|
+
except (KeyError,) as error:
|
|
186
|
+
raise DiamondDevError(f"Unknown agent adapter: {adapter_name}") from error
|
diamond_dev/commands.py
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"""External command and prompt construction."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True, slots=True)
|
|
11
|
+
class ComparisonBranchContext:
|
|
12
|
+
"""One implementation branch supplied to a comparison judge."""
|
|
13
|
+
|
|
14
|
+
agent_name: str
|
|
15
|
+
branch: str
|
|
16
|
+
repo_dir: Path
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True, slots=True)
|
|
20
|
+
class ComparisonPromptContext:
|
|
21
|
+
"""Context supplied to a comparison judge."""
|
|
22
|
+
|
|
23
|
+
base_branch: str
|
|
24
|
+
comparison_bundle_file_name: str
|
|
25
|
+
branches: Sequence[ComparisonBranchContext]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def build_codex_command(
|
|
29
|
+
repo_dir: Path,
|
|
30
|
+
prompt: str,
|
|
31
|
+
*,
|
|
32
|
+
model: str | None = None,
|
|
33
|
+
) -> tuple[str, ...]:
|
|
34
|
+
"""Build a non-interactive Codex command with full edit permissions."""
|
|
35
|
+
command = [
|
|
36
|
+
"codex",
|
|
37
|
+
"exec",
|
|
38
|
+
"-C",
|
|
39
|
+
str(repo_dir),
|
|
40
|
+
]
|
|
41
|
+
if model is not None:
|
|
42
|
+
command.extend(("-m", model))
|
|
43
|
+
command.extend(("--dangerously-bypass-approvals-and-sandbox", prompt))
|
|
44
|
+
return tuple(command)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def build_claude_print_command(
|
|
48
|
+
prompt: str,
|
|
49
|
+
*,
|
|
50
|
+
model: str | None = None,
|
|
51
|
+
) -> tuple[str, ...]:
|
|
52
|
+
"""Build a non-interactive Claude command with bypass permissions."""
|
|
53
|
+
command = ["claude"]
|
|
54
|
+
if model is not None:
|
|
55
|
+
command.extend(("--model", model))
|
|
56
|
+
command.extend((
|
|
57
|
+
"-p",
|
|
58
|
+
"--permission-mode",
|
|
59
|
+
"bypassPermissions",
|
|
60
|
+
"--dangerously-skip-permissions",
|
|
61
|
+
prompt,
|
|
62
|
+
))
|
|
63
|
+
return tuple(command)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def build_gemini_command(prompt: str, *, model: str | None = None) -> tuple[str, ...]:
|
|
67
|
+
"""Build a headless Gemini command."""
|
|
68
|
+
command = ["gemini"]
|
|
69
|
+
if model is not None:
|
|
70
|
+
command.extend(("-m", model))
|
|
71
|
+
command.extend(("-p", prompt, "--skip-trust", "-y"))
|
|
72
|
+
return tuple(command)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def build_coderabbit_review_command(base_branch: str) -> tuple[str, ...]:
|
|
76
|
+
"""Build a plain CodeRabbit review command."""
|
|
77
|
+
return ("coderabbit", "review", "--plain", "--base", base_branch)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def build_uv_sync_command() -> tuple[str, ...]:
|
|
81
|
+
"""Build a locked uv package install command."""
|
|
82
|
+
return ("uv", "sync", "--locked")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def build_pnpm_install_command() -> tuple[str, ...]:
|
|
86
|
+
"""Build a frozen pnpm package install command."""
|
|
87
|
+
return ("pnpm", "install", "--frozen-lockfile")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def build_gh_pr_list_command(head_branch: str) -> tuple[str, ...]:
|
|
91
|
+
"""Build a deterministic GitHub PR lookup command for a workflow branch."""
|
|
92
|
+
return (
|
|
93
|
+
"gh",
|
|
94
|
+
"pr",
|
|
95
|
+
"list",
|
|
96
|
+
"--head",
|
|
97
|
+
head_branch,
|
|
98
|
+
"--state",
|
|
99
|
+
"all",
|
|
100
|
+
"--json",
|
|
101
|
+
"number,state,url",
|
|
102
|
+
"--limit",
|
|
103
|
+
"1",
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def build_gh_pr_create_command(
|
|
108
|
+
*,
|
|
109
|
+
base_branch: str,
|
|
110
|
+
head_branch: str,
|
|
111
|
+
title: str,
|
|
112
|
+
body: str,
|
|
113
|
+
) -> tuple[str, ...]:
|
|
114
|
+
"""Build a deterministic GitHub PR creation command."""
|
|
115
|
+
return (
|
|
116
|
+
"gh",
|
|
117
|
+
"pr",
|
|
118
|
+
"create",
|
|
119
|
+
"--base",
|
|
120
|
+
base_branch,
|
|
121
|
+
"--head",
|
|
122
|
+
head_branch,
|
|
123
|
+
"--title",
|
|
124
|
+
title,
|
|
125
|
+
"--body",
|
|
126
|
+
body,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def build_gh_pr_edit_body_command(*, pr_url: str, body: str) -> tuple[str, ...]:
|
|
131
|
+
"""Build a deterministic GitHub PR body update command."""
|
|
132
|
+
return ("gh", "pr", "edit", pr_url, "--body", body)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def build_claude_interactive_review_command(
|
|
136
|
+
pr_number: str,
|
|
137
|
+
*,
|
|
138
|
+
model: str | None = None,
|
|
139
|
+
) -> tuple[str, ...]:
|
|
140
|
+
"""Build the final interactive Claude PR review command."""
|
|
141
|
+
command = ["claude"]
|
|
142
|
+
if model is not None:
|
|
143
|
+
command.extend(("--model", model))
|
|
144
|
+
command.extend((
|
|
145
|
+
"--permission-mode",
|
|
146
|
+
"bypassPermissions",
|
|
147
|
+
"--dangerously-skip-permissions",
|
|
148
|
+
f"/review {pr_number}",
|
|
149
|
+
))
|
|
150
|
+
return tuple(command)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def initial_implementation_prompt(
|
|
154
|
+
plan_file_name: str,
|
|
155
|
+
configured_prompt: str | None = None,
|
|
156
|
+
) -> str:
|
|
157
|
+
"""Return the initial implementation prompt for Codex or Claude."""
|
|
158
|
+
return _prompt_with_required_context(
|
|
159
|
+
configured_prompt,
|
|
160
|
+
fallback_prompt="Read and implement the supplied plan.",
|
|
161
|
+
required_lines=(
|
|
162
|
+
f"- Plan file: `{plan_file_name}`",
|
|
163
|
+
"- Commit your changes on the current branch.",
|
|
164
|
+
"- Do not push; diamond-dev will push committed work.",
|
|
165
|
+
),
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def comparison_implementation_prompt(
|
|
170
|
+
comparison_file_name: str,
|
|
171
|
+
configured_prompt: str | None = None,
|
|
172
|
+
) -> str:
|
|
173
|
+
"""Return the prompt for the opposite agent comparison implementation."""
|
|
174
|
+
return _prompt_with_required_context(
|
|
175
|
+
configured_prompt,
|
|
176
|
+
fallback_prompt="Implement the requested comparison follow-up changes.",
|
|
177
|
+
required_lines=(
|
|
178
|
+
f"- Comparison file: `{comparison_file_name}`",
|
|
179
|
+
"- Inspect the current branch first because this prompt may be rerun.",
|
|
180
|
+
"- Avoid duplicating work that is already applied.",
|
|
181
|
+
"- Commit your changes.",
|
|
182
|
+
"- Do not push; diamond-dev will push committed work.",
|
|
183
|
+
),
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def review_judgment_prompt(
|
|
188
|
+
review_file_name: str,
|
|
189
|
+
review_judgments_file_name: str,
|
|
190
|
+
review_provider: str,
|
|
191
|
+
review_judge: str,
|
|
192
|
+
configured_prompt: str | None = None,
|
|
193
|
+
) -> str:
|
|
194
|
+
"""Return the prompt asking Codex to classify CodeRabbit findings."""
|
|
195
|
+
return _prompt_with_required_context(
|
|
196
|
+
configured_prompt,
|
|
197
|
+
fallback_prompt="Evaluate each CodeRabbit review item.",
|
|
198
|
+
required_lines=(
|
|
199
|
+
f"- Review file: `{review_file_name}`",
|
|
200
|
+
f"- Structured judgment sidecar: `{review_judgments_file_name}`",
|
|
201
|
+
"- Write valid JSON to the structured judgment sidecar with "
|
|
202
|
+
"`schema_version`, `review_file`, `review_provider`, "
|
|
203
|
+
"`review_judge`, and `findings`.",
|
|
204
|
+
f"- Use `review_provider`: `{review_provider}`.",
|
|
205
|
+
f"- Use `review_judge`: `{review_judge}`.",
|
|
206
|
+
"- Each finding must have `id`, `decision`, `confidence`, and "
|
|
207
|
+
"`rationale`.",
|
|
208
|
+
"- Allowed decisions are `fix`, `decline`, and `needs_input`.",
|
|
209
|
+
"- Judge each item as (A) should fix, (B) decline fix, or "
|
|
210
|
+
"(C) requirements ambiguous or input needed.",
|
|
211
|
+
"- Append your judgements to the review file without removing "
|
|
212
|
+
"existing content.",
|
|
213
|
+
"- Commit the updated review file and structured judgment sidecar.",
|
|
214
|
+
"- Do not push; diamond-dev will push committed work.",
|
|
215
|
+
),
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def review_fix_prompt(
|
|
220
|
+
review_file_name: str,
|
|
221
|
+
review_judgments_file_name: str,
|
|
222
|
+
configured_prompt: str | None = None,
|
|
223
|
+
) -> str:
|
|
224
|
+
"""Return the prompt asking Codex to implement accepted review fixes."""
|
|
225
|
+
return _prompt_with_required_context(
|
|
226
|
+
configured_prompt,
|
|
227
|
+
fallback_prompt="Implement accepted CodeRabbit review fixes.",
|
|
228
|
+
required_lines=(
|
|
229
|
+
f"- Review file: `{review_file_name}`",
|
|
230
|
+
f"- Structured judgment sidecar: `{review_judgments_file_name}`",
|
|
231
|
+
"- If the structured judgment sidecar exists and is valid, implement "
|
|
232
|
+
"every finding with decision `fix`.",
|
|
233
|
+
"- If the sidecar is missing or invalid, fall back to legacy markdown "
|
|
234
|
+
"judgments and implement every review item judged as (A) should fix.",
|
|
235
|
+
"- Do not implement items judged as (B) decline fix.",
|
|
236
|
+
"- Do not implement findings with decision `decline` or `needs_input`.",
|
|
237
|
+
"- Leave items judged as (C) requirements ambiguous or input needed "
|
|
238
|
+
"unchanged.",
|
|
239
|
+
"- Inspect the current branch first because this prompt may be rerun.",
|
|
240
|
+
"- Avoid duplicating work that is already applied.",
|
|
241
|
+
"- Commit your changes.",
|
|
242
|
+
"- Do not push; diamond-dev will push committed work.",
|
|
243
|
+
),
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def gemini_comparison_prompt(
|
|
248
|
+
configured_prompt: str | None,
|
|
249
|
+
context: ComparisonPromptContext,
|
|
250
|
+
) -> str:
|
|
251
|
+
"""Return the comparison judgment prompt with mandatory run context."""
|
|
252
|
+
return _prompt_with_required_context(
|
|
253
|
+
configured_prompt,
|
|
254
|
+
fallback_prompt=_fallback_prompt(),
|
|
255
|
+
required_lines=_comparison_required_lines(context),
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _fallback_prompt() -> str:
|
|
260
|
+
return (
|
|
261
|
+
"Compare the implementation branches against the base branch. Evaluate "
|
|
262
|
+
"correctness, completeness, maintainability, tests, and risk. Recommend "
|
|
263
|
+
"one implementation as the base and describe any follow-up changes the "
|
|
264
|
+
"comparison fixer should apply."
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _comparison_required_lines(
|
|
269
|
+
context: ComparisonPromptContext,
|
|
270
|
+
) -> tuple[str, ...]:
|
|
271
|
+
branch_lines = tuple(
|
|
272
|
+
f"- {branch.agent_name} branch: `{branch.branch}` in `{branch.repo_dir}`"
|
|
273
|
+
for branch in context.branches
|
|
274
|
+
)
|
|
275
|
+
return (
|
|
276
|
+
f"- Base branch: `{context.base_branch}`",
|
|
277
|
+
f"- Comparison bundle: `{context.comparison_bundle_file_name}`",
|
|
278
|
+
"- Read the comparison bundle before judging branch quality.",
|
|
279
|
+
*branch_lines,
|
|
280
|
+
"- Write the final comparison to `comparison.md` in the current directory.",
|
|
281
|
+
"- Do not modify any implementation repository.",
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _prompt_with_required_context(
|
|
286
|
+
configured_prompt: str | None,
|
|
287
|
+
*,
|
|
288
|
+
fallback_prompt: str,
|
|
289
|
+
required_lines: tuple[str, ...],
|
|
290
|
+
) -> str:
|
|
291
|
+
stripped_prompt = configured_prompt.strip() if configured_prompt else ""
|
|
292
|
+
prompt = stripped_prompt or fallback_prompt
|
|
293
|
+
required_context = "\n".join(required_lines)
|
|
294
|
+
return f"{prompt}\n\nRequired context:\n{required_context}"
|