pantheon-opencode 1.1.1-beta.8 → 1.1.1

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.
@@ -1,226 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Validate all OpenCode agent .md files for correct frontmatter."""
3
-
4
- import sys
5
- from pathlib import Path
6
-
7
- import yaml
8
-
9
- VALID_FIELDS = {
10
- "description",
11
- "mode",
12
- "reasoning_effort",
13
- "permission",
14
- "temperature",
15
- "steps",
16
- "color",
17
- "hidden",
18
- "task_budget",
19
- "source",
20
- }
21
- VALID_MODES = {"primary", "subagent"}
22
- VALID_PERMISSION_KEYS = {
23
- "edit",
24
- "bash",
25
- "task",
26
- "skill",
27
- "read",
28
- "glob",
29
- "grep",
30
- "webfetch",
31
- "websearch",
32
- "question",
33
- "lsp",
34
- }
35
- VALID_PERMISSION_VALUES = {"allow", "deny", "ask"}
36
-
37
- AGENT_DIRS = [
38
- Path("/home/ils15/pantheon/platform/opencode/agents"),
39
- Path("/home/ils15/.config/opencode/agents"),
40
- ]
41
-
42
- errors = []
43
- warnings = []
44
- valid_count = 0
45
-
46
-
47
- def err(filename, field, msg):
48
- errors.append(f"❌ [{filename}] {field}: {msg}")
49
-
50
-
51
- def warn(filename, field, msg):
52
- warnings.append(f"⚠️ [{filename}] {field}: {msg}")
53
-
54
-
55
- def validate_permission_value(val, path_ctx, filename):
56
- if isinstance(val, str):
57
- if val not in VALID_PERMISSION_VALUES:
58
- err(
59
- filename,
60
- f"permission.{path_ctx}",
61
- f"invalid value '{val}'; must be one of {sorted(VALID_PERMISSION_VALUES)}",
62
- )
63
- elif isinstance(val, dict):
64
- for key, subval in val.items():
65
- validate_permission_value(subval, f"{path_ctx}.{key}", filename)
66
- else:
67
- err(
68
- filename,
69
- f"permission.{path_ctx}",
70
- f"unexpected type '{type(val).__name__}'; expected string or dict",
71
- )
72
-
73
-
74
- def validate_file(filepath): # noqa: C901, PLR0912, PLR0915
75
- global valid_count # noqa: PLW0603
76
- filename = filepath.name
77
- text = filepath.read_text(encoding="utf-8")
78
-
79
- if not text.startswith("---"):
80
- err(filename, "frontmatter", "file does not start with '---'")
81
- return
82
-
83
- parts = text.split("---")
84
- if len(parts) < 3: # noqa: PLR2004
85
- err(filename, "frontmatter", "missing closing '---' delimiter")
86
- return
87
-
88
- raw_yaml = parts[1]
89
- try:
90
- data = yaml.safe_load(raw_yaml)
91
- except yaml.YAMLError as e:
92
- err(filename, "frontmatter", f"YAML parse error: {e}")
93
- return
94
-
95
- if not isinstance(data, dict):
96
- err(filename, "frontmatter", "frontmatter is not a mapping")
97
- return
98
-
99
- # Check for unknown fields
100
- unknown = set(data.keys()) - VALID_FIELDS
101
- for field in sorted(unknown):
102
- err(filename, field, f"unknown field '{field}'; valid: {sorted(VALID_FIELDS)}")
103
-
104
- # description: must be present
105
- if "description" not in data:
106
- err(filename, "description", "description field is required")
107
- elif not isinstance(data["description"], str) or not data["description"].strip():
108
- err(filename, "description", "must be a non-empty string")
109
-
110
- # mode: must be present and valid
111
- if "mode" not in data:
112
- err(filename, "mode", "mode field is required")
113
- elif data["mode"] not in VALID_MODES:
114
- err(
115
- filename,
116
- "mode",
117
- f"invalid value '{data['mode']}'; must be one of {sorted(VALID_MODES)}",
118
- )
119
-
120
- # permission: validate keys and values
121
- if "permission" in data:
122
- perm = data["permission"]
123
- if isinstance(perm, dict):
124
- for key in perm:
125
- if key not in VALID_PERMISSION_KEYS:
126
- err(
127
- filename,
128
- f"permission.{key}",
129
- f"invalid permission key '{key}'; valid: {sorted(VALID_PERMISSION_KEYS)}",
130
- )
131
- validate_permission_value(perm[key], key, filename)
132
- else:
133
- err(
134
- filename,
135
- "permission",
136
- f"unexpected type '{type(perm).__name__}'; expected mapping",
137
- )
138
-
139
- # temperature: between 0.0 and 2.0
140
- if "temperature" in data:
141
- t = data["temperature"]
142
- if not isinstance(t, (int, float)):
143
- err(filename, "temperature", f"expected a number, got '{type(t).__name__}'")
144
- elif t < 0.0 or t > 2.0: # noqa: PLR2004
145
- err(filename, "temperature", f"value {t} is out of range [0.0, 2.0]")
146
-
147
- # steps: must be a positive integer
148
- if "steps" in data:
149
- s = data["steps"]
150
- if not isinstance(s, int) or isinstance(s, bool):
151
- err(
152
- filename,
153
- "steps",
154
- f"expected a positive integer, got '{type(s).__name__}'",
155
- )
156
- elif s < 1:
157
- err(filename, "steps", f"value {s} is not a positive integer")
158
-
159
- # Body content checks
160
- body = "---".join(parts[2:])
161
-
162
- bash_value = None
163
- if isinstance(data.get("permission"), dict):
164
- bash_value = data["permission"].get("bash")
165
-
166
- if bash_value == "deny":
167
- if "⛔ TOOLS NOT AVAILABLE" not in body:
168
- err(
169
- filename,
170
- "body",
171
- "permission.bash is 'deny' but body is missing '## ⛔ TOOLS NOT AVAILABLE' section",
172
- )
173
- elif "bash" not in body.split("⛔ TOOLS NOT AVAILABLE")[1].split("\n")[0:20]:
174
- warn(
175
- filename,
176
- "body",
177
- "permission.bash is 'deny' but 'bash' may not be listed as unavailable in the section",
178
- )
179
-
180
- edit_value = None
181
- if isinstance(data.get("permission"), dict):
182
- edit_value = data["permission"].get("edit")
183
-
184
- if edit_value == "deny":
185
- edit_mentions = body.lower().count("edit")
186
- if edit_mentions > 0:
187
- warn(
188
- filename,
189
- "body",
190
- f"permission.edit is 'deny' but 'edit' appears {edit_mentions} time(s) in body",
191
- )
192
-
193
- valid_count += 1
194
-
195
-
196
- def main():
197
- all_files = []
198
- for d in AGENT_DIRS:
199
- if not d.exists():
200
- print(f"⚠️ Config error: directory not found: {d}", file=sys.stderr)
201
- sys.exit(2)
202
- all_files.extend(sorted(d.glob("*.md")))
203
-
204
- if not all_files:
205
- print("⚠️ Config error: no agent .md files found", file=sys.stderr)
206
- sys.exit(2)
207
-
208
- for f in all_files:
209
- validate_file(f)
210
-
211
- for msg in errors:
212
- print(msg, file=sys.stderr)
213
- for msg in warnings:
214
- print(msg, file=sys.stderr)
215
-
216
- print(
217
- f"✅ {valid_count} files valid, {len(warnings)} warnings, {len(errors)} errors"
218
- )
219
-
220
- if errors:
221
- sys.exit(1)
222
- sys.exit(0)
223
-
224
-
225
- if __name__ == "__main__":
226
- main()