main
py 131 lines 4.44 KB
Raw
1 import json
2 from pathlib import Path
3
4 _DIR = Path(__file__).parent.parent
5 _CFG = None
6 _TMPL = None
7 _CHECKLIST_GUIDANCE = None
8
9
10 def _load_config() -> dict:
11 global _CFG
12 if _CFG is not None:
13 return _CFG
14
15 path = _DIR / "webui" / "plugin-validator-checks.json"
16 try:
17 _CFG = json.loads(path.read_text())
18 return _CFG
19 except Exception as e:
20 raise RuntimeError(f"Unable to load plugin validator checks: {e}") from e
21
22
23 def _load_template() -> str:
24 global _TMPL
25 if _TMPL is not None:
26 return _TMPL
27
28 path = _DIR / "webui" / "plugin-validator-prompt.md"
29 try:
30 _TMPL = path.read_text()
31 return _TMPL
32 except Exception as e:
33 raise RuntimeError(f"Unable to load plugin validator prompt template: {e}") from e
34
35
36 def _load_guidance() -> str:
37 global _CHECKLIST_GUIDANCE
38 if _CHECKLIST_GUIDANCE is not None:
39 return _CHECKLIST_GUIDANCE
40
41 path = _DIR / "webui" / "plugin-validator-guidance.md"
42 try:
43 _CHECKLIST_GUIDANCE = path.read_text().strip()
44 return _CHECKLIST_GUIDANCE
45 except Exception as e:
46 raise RuntimeError(f"Unable to load plugin validator guidance: {e}") from e
47
48
49 def _sanitize_target(value: str) -> str:
50 return (value or "").strip().replace("{", "(").replace("}", ")")
51
52
53 def _target_reference(source_type: str, target: str) -> str:
54 target = _sanitize_target(target)
55 if source_type == "local" and target and "/" not in target and "\\" not in target:
56 return f"usr/plugins/{target}/"
57 return target
58
59
60 def _source_label(source_type: str) -> str:
61 return {
62 "local": "Local Plugin",
63 "git": "Git Repository",
64 "zip": "Uploaded ZIP",
65 }.get(source_type, "Plugin Source")
66
67
68 def _source_instructions(source_type: str, target: str, cleanup_target: str | None = None) -> str:
69 target_ref = _target_reference(source_type, target)
70 cleanup_ref = _sanitize_target(cleanup_target or target_ref)
71
72 if source_type == "git":
73 return (
74 f"Clone `{target_ref}` to a temporary directory outside the workspace, such as "
75 "`/tmp/plugin-validate-$(date +%s)`. Validate the cloned files there. After the review, "
76 "run `rm -rf /tmp/plugin-validate-*` and verify cleanup with `ls /tmp/plugin-validate-* 2>&1`."
77 )
78
79 if source_type == "zip":
80 return (
81 f"The ZIP has already been extracted to `{target_ref}`. Validate the plugin from that extracted "
82 "directory only. Do not install or move it. After the review, delete that extracted directory "
83 f"with `rm -rf \"{cleanup_ref}\"` and verify cleanup with `ls \"{cleanup_ref}\" 2>&1`."
84 )
85
86 return (
87 f"Read the plugin directly from `{target_ref}`. Do not clone, move, or modify the plugin. "
88 "No temporary cleanup is required for this source."
89 )
90
91
92 def build_prompt(
93 source_type: str,
94 target: str,
95 checks: list | None = None,
96 cleanup_target: str | None = None,
97 ) -> str:
98 cfg = _load_config()
99 ratings, all_checks = cfg["ratings"], cfg["checks"]
100 keys = list(all_checks.keys()) if checks is None else [k for k in checks if k in all_checks]
101 prompt_template = _load_template()
102
103 subs = {
104 "SOURCE_LABEL": _source_label(source_type),
105 "TARGET_REFERENCE": _target_reference(source_type, target),
106 "SOURCE_INSTRUCTIONS": _source_instructions(source_type, target, cleanup_target),
107 "SELECTED_CHECKS": (
108 "\n".join(f"- **{all_checks[k]['label']}**" for k in keys)
109 if keys
110 else "- (no validation phases selected)"
111 ),
112 "CHECK_DETAILS": (
113 "\n\n".join(
114 f"#### {c['label']}\n{c['detail']}\n\nCriteria:\n"
115 + "\n".join(f" - {ratings[level]['icon']} {desc}" for level, desc in c["criteria"].items())
116 for c in (all_checks[k] for k in keys)
117 )
118 if keys
119 else "(no validation phases selected)"
120 ),
121 "CHECKLIST_GUIDANCE": _load_guidance(),
122 "STATUS_LEGEND": "\n".join(f"- {r['icon']} **{r['label']}**" for r in ratings.values()),
123 "RATING_ICONS": "/".join(r["icon"] for r in ratings.values()),
124 "RATING_PASS": ratings["pass"]["icon"],
125 "RATING_WARNING": ratings["warning"]["icon"],
126 "RATING_FAIL": ratings["fail"]["icon"],
127 }
128 prompt = prompt_template
129 for key, val in subs.items():
130 prompt = prompt.replace(f"{{{{{key}}}}}", val)
131 return prompt