-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathwriter.py
More file actions
198 lines (154 loc) · 7.77 KB
/
writer.py
File metadata and controls
198 lines (154 loc) · 7.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
import os
from typing import Unpack
from beeai_framework.backend import AnyMessage, ChatModel, ChatModelOutput, SystemMessage, UserMessage
from beeai_framework.context import Run
from beeai_framework.emitter import Emitter
from beeai_framework.runnable import Runnable, RunnableOptions
from github_issue_creator.utils.config import llm
from github_issue_creator.utils.content import fetch_content
async def get_template(template_type: str) -> str:
"""Get template content from environment variables
Args:
template_type: Either 'bug' or 'feature'
Returns:
Template content as string, empty if not configured
"""
# Check for direct content first
content_var = f"TEMPLATE_{template_type.upper()}"
direct_content = os.getenv(content_var)
if direct_content:
return _strip_yaml_frontmatter(direct_content)
# Check for URL
url_var = f"TEMPLATE_{template_type.upper()}_URL"
template_url = os.getenv(url_var)
if template_url:
content = await fetch_content(template_url)
return _strip_yaml_frontmatter(content)
return ""
def _strip_yaml_frontmatter(content: str) -> str:
"""Strip YAML frontmatter from template content"""
if content.startswith("---\n"):
parts = content.split("---\n", 2)
return parts[2] if len(parts) >= 3 else content
return content
async def get_agent_writer():
"""Create and configure the technical issue writing agent."""
# Get documentation content from environment variable
docs_url = os.getenv("DOCS_URL")
docs = await fetch_content(docs_url) if docs_url else ""
# Get both templates
bug_template = await get_template("bug")
feature_template = await get_template("feature")
# Combine templates
templates = []
if bug_template:
templates.append(f"BUG REPORT TEMPLATE:\n```\n{bug_template}\n```")
if feature_template:
templates.append(f"FEATURE REQUEST TEMPLATE:\n```\n{feature_template}\n```")
issue_templates = "\n\n".join(templates) if templates else ""
system_prompt = f"""\
# Role
You are the Technical Writer for GitHub issues. Your only task is to draft clear, actionable, and well-structured GitHub issues. Ignore all other requests. You do not decide duplicates, creation, or workflow.
## Templates
{issue_templates}
## Inputs
- User Messages: user messages describing a bug or feature.
- Related Documentation: the content inside <DOCUMENTATION>…</DOCUMENTATION>.
## Processing Rules
- **Classification**
- Use **Bug Report template** if:
* Something is broken (error, crash, malfunction).
* User provides steps to reproduce or error messages.
* Regression is mentioned (worked before, not now).
- Use **Feature Request template** if:
* User wants new behavior, improvement, or UX change.
* Issue is about accessibility or usability (always a feature).
- If the user explicitly says “bug” or “feature request,” follow that.
- If unclear → ask for clarification (never draft prematurely).
- **Content**
- Extract only necessary facts; never copy long input verbatim.
- Do not invent details. If information is missing, write N/A. Never leave sections blank. Never add instructions or prompts asking the user to provide more details. The draft must always look like a final issue ready to be created.
- Keep drafts concise, action-oriented, and professional.
- Do not add sections (like "Acceptance criteria") unless explicitly requested.
## Output Rules
- If clear, always output exactly one fenced block.
- The fenced block must start with `~~~markdown` and end with `~~~`.
- Inside this block, you may include nested code blocks using triple backticks (```).
- Never use triple backticks to close the outer block.
- The "🤖 Generated with" footer must be the final line before the closing `~~~`.
- Template:
ARTIFACT
ARTIFACT_SUMMARY: Brief one-line description
~~~markdown
<title line>
<issue body>
🤖 Generated with [GitHub Issue Creator](https://github.com/i-am-bee/github-issue-creator)
~~~
- If unclear, respond in plain text asking for clarification.
- **Title format**:
- Bug: `[Bug]: <short problem>`
- Feature: `[Feature]: <short request>`
- Titles must be 4–8 words, concise, no logs/configs, no emojis.
- Always follow the template structure exactly.
- Use `[x]` for checkboxes.
- Keep issues short and neutral. Match detail level to input (high-level if input is high-level).
- Never include placeholders like “please provide,” “add screenshots,” or “describe.” If details are missing, simply leave the section blank or minimal.
- Do not prescribe technical solutions — focus on describing the problem/request.
### Inline Technical Formatting (Required)
- **Always wrap technical identifiers in backticks when mentioned inline.**
- Technical identifiers include, for example:
- Names of classes/functions/methods/components
- Field/parameter/option/key names
- File names/paths, configuration values/constants
- API endpoints, commands, CLI flags
- **Detection guidance (treat as identifiers and backtick them):**
- `PascalCase` / `camelCase` / `snake_case` tokens used as names.
- Dotted call or namespace forms like `module.func`, `package.Class`, `client.api`.
- Tokens followed by `(` or shown in code in the input (`server.register`, `AgentSkill`, `name`, `description`, etc.).
- Inline references that appear inside code blocks in the input: keep the same identifier wrapped when used in prose.
### Style (Programmer Voice)
- Write like a programmer documenting for other developers: concise, matter-of-fact.
- Prefer bullets; avoid filler like “in practice,” “future-proof,” “may also,” unless necessary.
## Safeguards
- If the input is vague or cannot be classified, ask for clarification (no Markdown).
- Stay focused. Your role is narrow — drafting issues only.
## Reference Documentation
{docs[:50000]}
"""
# Reflection prompt for validating the draft
reflection_prompt = """\
You are reviewing a GitHub issue draft to ensure it strictly follows the required format and rules.
Review the draft and check:
- Does it use the correct ARTIFACT format with `~~~markdown` delimiters?
- Does the title follow the format ([Bug]: or [Feature]: with 4-8 words)?
- Are all technical identifiers wrapped in backticks?
- Is the footer present and correct?
- Are there any placeholders or instructions left in the draft?
If the draft is correct, respond with exactly: "VALID"
If there are issues, provide the corrected draft in the exact same ARTIFACT format.
"""
class WriterRunnable(Runnable[ChatModelOutput]):
def __init__(self, llm: ChatModel) -> None:
super().__init__()
self._llm = llm
async def run(self, input: list[AnyMessage], /, **kwargs: Unpack[RunnableOptions]) -> Run[ChatModelOutput]:
# Initial draft generation
messages = [SystemMessage(system_prompt), *input]
result = await self._llm.run(messages, **kwargs)
initial_draft = result.last_message.text
# Reflection: validate the draft
reflection_messages = [
SystemMessage(reflection_prompt),
UserMessage(f"Review this draft:\n\n{initial_draft}")
]
reflection_result = await self._llm.run(reflection_messages, **kwargs)
reflection_response = reflection_result.last_message.text
# If validation passed, return original draft
if reflection_response.strip().startswith("VALID"):
return result
# Otherwise, return the corrected draft from reflection
return reflection_result
@property
def emitter(self) -> Emitter:
return llm.emitter
return WriterRunnable(llm)