feat: Add Grafana auto-update & improve classifier
Add auto-update for Grafana on Synology via SSH + docker compose, with review examples and a context.txt describing the specific setup (Prometheus + Node Exporter, no plugins) so the classifier can ignore plugin-only breaking changes. The classifier now loads an optional context.txt per service to tailor decisions to the actual deployment. It also truncates large release notes intelligently, keeping important sections (breaking changes, deprecations, migration notes) while dropping long PR lists that add no signal. test_classifier.py now also loads context.txt and accepts any service name as argument.
This commit is contained in:
parent
67a9509eaa
commit
ea90b8569b
10 changed files with 185 additions and 1 deletions
|
|
@ -49,7 +49,10 @@ class Classifier:
|
|||
if not examples:
|
||||
return {"action": "manual", "reason": "No review examples configured; defaulting to manual review."}
|
||||
|
||||
messages = self._build_messages(service_dir.name, examples, release_notes, current, latest)
|
||||
release_notes = self._truncate(release_notes)
|
||||
context_file = service_dir / "context.txt"
|
||||
context = context_file.read_text(encoding="utf-8").strip() if context_file.is_file() else None
|
||||
messages = self._build_messages(service_dir.name, examples, release_notes, current, latest, context)
|
||||
|
||||
try:
|
||||
payload = json.dumps({
|
||||
|
|
@ -85,13 +88,19 @@ class Classifier:
|
|||
release_notes: str,
|
||||
current: str,
|
||||
latest: str,
|
||||
context: Optional[str] = None,
|
||||
) -> List[Dict[str, str]]:
|
||||
system_prompt = (
|
||||
f"You are an update safety classifier for self-hosted {service_name} instances.\n"
|
||||
"Given release notes for a software update, decide whether the update can be applied\n"
|
||||
"automatically (action: auto) or requires the admin to intervene manually before or\n"
|
||||
"during the update (action: manual).\n\n"
|
||||
)
|
||||
if context:
|
||||
system_prompt += f"Instance context: {context}\n\n"
|
||||
system_prompt += (
|
||||
"Rules:\n"
|
||||
"- Only flag as manual if the breaking changes actually affect this specific setup.\n"
|
||||
"- Classify only THIS release, not future or past releases mentioned in the notes.\n"
|
||||
"- Warnings or announcements about breaking changes in upcoming versions do NOT\n"
|
||||
" make the current release manual.\n"
|
||||
|
|
@ -113,3 +122,36 @@ class Classifier:
|
|||
"content": f"Upgrading from {current} to {latest}.\n\nRelease notes:\n{release_notes}",
|
||||
})
|
||||
return messages
|
||||
|
||||
@staticmethod
|
||||
def _truncate(text: str, max_chars: int = 16000) -> str:
|
||||
if len(text) <= max_chars:
|
||||
return text
|
||||
|
||||
# Split into sections by markdown headers
|
||||
import re
|
||||
sections = re.split(r'(?=^#{1,4}\s)', text, flags=re.MULTILINE)
|
||||
|
||||
important_keywords = re.compile(
|
||||
r'breaking|deprecat|migrat|important|caution|warning|manual|required|must|before.*upgrad',
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
# Always keep the intro (first section) and any important sections
|
||||
kept = []
|
||||
skipped = 0
|
||||
for i, section in enumerate(sections):
|
||||
if i == 0 or important_keywords.search(section[:500]):
|
||||
kept.append(section)
|
||||
else:
|
||||
skipped += 1
|
||||
|
||||
result = "".join(kept)
|
||||
if skipped:
|
||||
result += f"\n\n[... {skipped} sections of features/bug fixes omitted ...]"
|
||||
|
||||
# Final safety truncation if still too long
|
||||
if len(result) > max_chars:
|
||||
result = result[:max_chars] + "\n\n[... truncated ...]"
|
||||
|
||||
return result
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue