Files
Momento/.agent/skills/suno-feedback-elicitor/scripts/parse-feedback.py
Antigravity bd495be965
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 12s
feat: design system overhaul — sidebar, AI chats, settings, brainstorm, color cleanup
- Sidebar: dynamic brand-accent colors, brainstorm section restyled
- AI chat general: popup panel with expand/collapse, hides when contextual AI open
- AI chat contextual: tabs reordered (Actions first), X close button, height fix
- Settings: all tabs restyled, 6 new color presets (sage, terracotta, iron, etc.)
- Global color cleanup: emerald/orange hardcoded → brand-accent dynamic
- Brainstorm page: orange → brand-accent throughout
- PageEntry animation component added to key pages
- Floating AI button: bg-brand-accent instead of hardcoded black
- i18n: all 15 locales updated with new AI/billing keys
- Billing: freemium quota tracking, BYOK, stripe subscription scaffolding
- Admin: integrated into new design
- AGENTS.md + CLAUDE.md project rules added
2026-05-16 12:59:30 +00:00

302 lines
11 KiB
Python

#!/usr/bin/env python3
# /// script
# requires-python = ">=3.10"
# dependencies = []
# ///
"""
Parse and validate structured feedback input for headless mode.
Accepts JSON feedback input and extracts structured dimensions for
the Feedback Elicitor skill. Validates required fields and normalizes
the input structure for downstream processing.
Exit codes:
0 = valid input, structured output returned
1 = validation failed (invalid structure or missing required fields)
2 = runtime error
"""
import argparse
import json
import sys
from pathlib import Path
from typing import Any
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent / "_shared"))
from suno_constants import VALID_MODELS
VALID_DIMENSIONS = [
"music",
"vocals",
"energy",
"structure",
"lyrics",
"vibe",
"production",
"tempo",
"instrumentation",
"length",
"quality",
]
VALID_FEEDBACK_TYPES = ["clear", "positive", "vague", "contradictory", "technical"]
def validate_feedback_input(data: dict[str, Any]) -> list[dict[str, Any]]:
"""Validate structured feedback input and return findings."""
findings = []
# feedback_text is required
if "feedback_text" not in data or not data["feedback_text"].strip():
findings.append({
"severity": "critical",
"category": "structure",
"location": {"field": "feedback_text"},
"issue": "Missing or empty feedback_text field",
"fix": "Provide feedback_text with the user's feedback about their Suno generation",
})
# Validate optional fields if present
if "model" in data and data["model"] not in VALID_MODELS:
findings.append({
"severity": "info",
"category": "consistency",
"location": {"field": "model"},
"issue": f"Unrecognized model '{data['model']}' — recommendations may not be model-optimized. Known models: {', '.join(sorted(VALID_MODELS))}",
"fix": "This is informational — the model name will be passed through. Known models receive model-specific recommendations.",
})
if "dimensions" in data:
if not isinstance(data["dimensions"], list):
findings.append({
"severity": "high",
"category": "structure",
"location": {"field": "dimensions"},
"issue": "dimensions must be an array",
"fix": "Provide dimensions as an array of strings",
})
else:
for dim in data["dimensions"]:
if dim not in VALID_DIMENSIONS:
findings.append({
"severity": "low",
"category": "consistency",
"location": {"field": "dimensions", "value": dim},
"issue": f"Unknown dimension '{dim}'. Valid: {', '.join(VALID_DIMENSIONS)}",
"fix": f"Use one of: {', '.join(VALID_DIMENSIONS)}",
})
if "feedback_type" in data and data["feedback_type"] not in VALID_FEEDBACK_TYPES:
findings.append({
"severity": "medium",
"category": "consistency",
"location": {"field": "feedback_type"},
"issue": f"Unknown feedback_type '{data['feedback_type']}'. Valid: {', '.join(VALID_FEEDBACK_TYPES)}",
"fix": f"Use one of: {', '.join(VALID_FEEDBACK_TYPES)}",
})
if "slider_settings" in data:
sliders = data["slider_settings"]
if not isinstance(sliders, dict):
findings.append({
"severity": "medium",
"category": "structure",
"location": {"field": "slider_settings"},
"issue": "slider_settings must be an object",
"fix": "Provide as {\"weirdness\": 50, \"style_influence\": 50}",
})
else:
for key in ["weirdness", "style_influence"]:
if key in sliders:
val = sliders[key]
if not isinstance(val, (int, float)) or val < 0 or val > 100:
findings.append({
"severity": "medium",
"category": "consistency",
"location": {"field": f"slider_settings.{key}"},
"issue": f"{key} must be a number between 0 and 100",
"fix": f"Set {key} to a value between 0 and 100",
})
return findings
def extract_structured_output(data: dict[str, Any]) -> dict[str, Any]:
"""Extract and normalize structured feedback for downstream processing."""
output = {
"feedback_text": data.get("feedback_text", "").strip(),
"context": {
"original_style_prompt": data.get("original_style_prompt", ""),
"original_lyrics": data.get("original_lyrics", ""),
"band_profile": data.get("band_profile", ""),
"model": data.get("model", ""),
"slider_settings": data.get("slider_settings", {}),
"intent": data.get("intent", ""),
},
"pre_categorized": {
"feedback_type": data.get("feedback_type", ""),
"dimensions": data.get("dimensions", []),
},
}
# Strip empty context fields
output["context"] = {k: v for k, v in output["context"].items() if v}
output["pre_categorized"] = {k: v for k, v in output["pre_categorized"].items() if v}
return output
def main():
parser = argparse.ArgumentParser(
description="Parse and validate structured feedback input for Suno Feedback Elicitor headless mode.",
epilog="""
Input JSON schema:
Required:
feedback_text (string) - The user's feedback about their Suno generation
Optional context:
original_style_prompt (string) - Style prompt used for generation
original_lyrics (string) - Lyrics used for generation
band_profile (string) - Band profile name used
model (string) - Suno model used (v4.5-all, v4 Pro, v4.5 Pro, v4.5+ Pro, v5 Pro)
slider_settings (object) - {weirdness: 0-100, style_influence: 0-100}
intent (string) - What the user was going for
Optional pre-categorization:
feedback_type (string) - clear, positive, vague, contradictory
dimensions (array) - Problem dimensions: music, vocals, energy, structure, lyrics, vibe, production, tempo, instrumentation
Example:
echo '{"feedback_text": "The guitar is too loud", "model": "v5 Pro"}' | python3 parse-feedback.py --stdin
python3 parse-feedback.py --input feedback.json
""",
formatter_class=argparse.RawDescriptionHelpFormatter,
)
input_group = parser.add_mutually_exclusive_group(required=True)
input_group.add_argument("--input", "-i", help="Path to feedback JSON file")
input_group.add_argument("--stdin", action="store_true", help="Read JSON from stdin")
parser.add_argument("--output", "-o", help="Output file path (default: stdout)")
parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output to stderr")
args = parser.parse_args()
try:
if args.stdin:
raw = sys.stdin.read()
else:
with open(args.input, "r") as f:
raw = f.read()
data = json.loads(raw)
except json.JSONDecodeError as e:
result = {
"script": "parse-feedback",
"version": "1.0.0",
"status": "fail",
"findings": [{
"severity": "critical",
"category": "structure",
"location": {"field": "root"},
"issue": f"Invalid JSON: {e}",
"fix": "Provide valid JSON input",
}],
"summary": {"total": 1, "critical": 1, "high": 0, "medium": 0, "low": 0, "info": 0},
}
output_json = json.dumps(result, indent=2)
if args.output:
with open(args.output, "w") as f:
f.write(output_json)
else:
print(output_json)
sys.exit(1)
except FileNotFoundError:
print(json.dumps({
"script": "parse-feedback",
"version": "1.0.0",
"status": "fail",
"findings": [{
"severity": "critical",
"category": "structure",
"location": {"field": "input"},
"issue": f"File not found: {args.input}",
"fix": "Provide a valid file path",
}],
"summary": {"total": 1, "critical": 1, "high": 0, "medium": 0, "low": 0, "info": 0},
}, indent=2))
sys.exit(1)
if not isinstance(data, dict):
result = {
"script": "parse-feedback",
"version": "1.0.0",
"status": "fail",
"findings": [{
"severity": "critical",
"category": "structure",
"location": {"field": "root"},
"issue": "Input must be a JSON object",
"fix": "Provide a JSON object with at least a feedback_text field",
}],
"summary": {"total": 1, "critical": 1, "high": 0, "medium": 0, "low": 0, "info": 0},
}
output_json = json.dumps(result, indent=2)
if args.output:
with open(args.output, "w") as f:
f.write(output_json)
else:
print(output_json)
sys.exit(1)
findings = validate_feedback_input(data)
has_critical = any(f["severity"] == "critical" for f in findings)
has_high = any(f["severity"] == "high" for f in findings)
has_actionable = any(f["severity"] in ("critical", "high", "medium", "low") for f in findings)
if has_critical or has_high:
status = "fail"
elif has_actionable:
status = "warning"
else:
status = "pass"
structured_output = extract_structured_output(data) if not has_critical else None
severity_counts = {"critical": 0, "high": 0, "medium": 0, "low": 0, "info": 0}
for f in findings:
sev = f["severity"]
if sev in severity_counts:
severity_counts[sev] += 1
result = {
"script": "parse-feedback",
"version": "1.0.0",
"status": status,
"findings": findings,
"summary": {
"total": len(findings),
**severity_counts,
},
}
if structured_output:
result["parsed"] = structured_output
if args.verbose:
print(f"[parse-feedback] Status: {status}, Findings: {len(findings)}", file=sys.stderr)
output_json = json.dumps(result, indent=2)
if args.output:
with open(args.output, "w") as f:
f.write(output_json)
if args.verbose:
print(f"[parse-feedback] Output written to {args.output}", file=sys.stderr)
else:
print(output_json)
sys.exit(0 if status in ("pass", "warning") else 1)
if __name__ == "__main__":
main()