tribunal-kit 2.4.0 → 2.4.3

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,177 +1,177 @@
1
- #!/usr/bin/env python3
2
- """
3
- patch_skills_meta.py — Injects version/freshness metadata into SKILL.md frontmatter.
4
-
5
- Adds the following fields to YAML frontmatter if missing:
6
- version: 1.0.0
7
- last-updated: 2026-03-12
8
- applies-to-model: gemini-2.5-pro, claude-3-7-sonnet
9
-
10
- Usage:
11
- python .agent/scripts/patch_skills_meta.py .
12
- python .agent/scripts/patch_skills_meta.py . --dry-run
13
- python .agent/scripts/patch_skills_meta.py . --skill python-pro
14
- """
15
-
16
- import os
17
- import sys
18
- import argparse
19
- import re
20
- from pathlib import Path
21
-
22
- RED = "\033[91m"
23
- GREEN = "\033[92m"
24
- YELLOW = "\033[93m"
25
- BLUE = "\033[94m"
26
- BOLD = "\033[1m"
27
- RESET = "\033[0m"
28
-
29
- META_FIELDS = {
30
- "version": "1.0.0",
31
- "last-updated": "2026-03-12",
32
- "applies-to-model": "gemini-2.5-pro, claude-3-7-sonnet",
33
- }
34
-
35
-
36
- def header(title: str) -> None:
37
- print(f"\n{BOLD}{BLUE}━━━ {title} ━━━{RESET}")
38
-
39
-
40
- def ok(msg: str) -> None:
41
- print(f" {GREEN}✅ {msg}{RESET}")
42
-
43
-
44
- def skip(msg: str) -> None:
45
- print(f" {YELLOW}⏭️ {msg}{RESET}")
46
-
47
-
48
- def warn(msg: str) -> None:
49
- print(f" {YELLOW}⚠️ {msg}{RESET}")
50
-
51
-
52
- def fail(msg: str) -> None:
53
- print(f" {RED}❌ {msg}{RESET}")
54
-
55
-
56
- def patch_frontmatter(content: str) -> tuple[str, list[str]]:
57
- """
58
- Parse the YAML frontmatter block and inject missing meta fields.
59
- Returns (patched_content, list_of_added_fields).
60
- If no frontmatter is found, injects a minimal one.
61
- """
62
- added: list[str] = []
63
-
64
- # Match frontmatter block: starts and ends with ---
65
- fm_pattern = re.compile(r"^---\r?\n(.*?)\r?\n---", re.DOTALL)
66
- match = fm_pattern.match(content)
67
-
68
- if not match:
69
- # No frontmatter — prepend a minimal block
70
- new_fm_lines = ["---"]
71
- for key, value in META_FIELDS.items():
72
- new_fm_lines.append(f"{key}: {value}")
73
- added.append(key)
74
- new_fm_lines.append("---")
75
- new_fm = "\n".join(new_fm_lines)
76
- return new_fm + "\n\n" + content, added
77
-
78
- fm_text = match.group(1)
79
- fm_end = match.end()
80
-
81
- # Parse existing lines preserving order
82
- existing_keys = set()
83
- for line in fm_text.splitlines():
84
- m = re.match(r"^([a-zA-Z0-9_-]+)\s*:", line)
85
- if m:
86
- existing_keys.add(m.group(1))
87
-
88
- # Build new frontmatter
89
- new_fm_lines = fm_text.rstrip().splitlines()
90
- for key, value in META_FIELDS.items():
91
- if key not in existing_keys:
92
- new_fm_lines.append(f"{key}: {value}")
93
- added.append(key)
94
-
95
- if not added:
96
- return content, []
97
-
98
- new_fm_block = "---\n" + "\n".join(new_fm_lines) + "\n---"
99
- patched = new_fm_block + content[fm_end:]
100
- return patched, added
101
-
102
-
103
- def process_skill(skill_path: Path, dry_run: bool) -> str:
104
- """Process a single SKILL.md. Returns 'updated', 'skipped', or 'error'."""
105
- try:
106
- content = skill_path.read_text(encoding="utf-8")
107
- patched, added = patch_frontmatter(content)
108
-
109
- skill_name = skill_path.parent.name
110
-
111
- if not added:
112
- skip(f"{skill_name} — all meta fields present")
113
- return "skipped"
114
-
115
- field_list = ", ".join(added)
116
- if dry_run:
117
- warn(f"[DRY RUN] {skill_name} — would add: {field_list}")
118
- return "updated"
119
-
120
- skill_path.write_text(patched, encoding="utf-8")
121
- ok(f"{skill_name} — added: {field_list}")
122
- return "updated"
123
-
124
- except Exception as e:
125
- fail(f"{skill_path.parent.name} — {e}")
126
- return "error"
127
-
128
-
129
- def main() -> None:
130
- parser = argparse.ArgumentParser(
131
- description="Injects version/freshness metadata into SKILL.md frontmatter"
132
- )
133
- parser.add_argument("path", help="Project root directory")
134
- parser.add_argument("--dry-run", action="store_true", help="Show changes without writing")
135
- parser.add_argument("--skill", help="Only patch a specific skill by name")
136
- args = parser.parse_args()
137
-
138
- project_root = Path(args.path).resolve()
139
- skills_dir = project_root / ".agent" / "skills"
140
-
141
- if not skills_dir.is_dir():
142
- fail(f"Skills directory not found: {skills_dir}")
143
- sys.exit(1)
144
-
145
- print(f"{BOLD}Tribunal — patch_skills_meta.py{RESET}")
146
- if args.dry_run:
147
- print(f" {YELLOW}DRY RUN — no files will be written{RESET}")
148
- print(f"Skills dir: {skills_dir}\n")
149
-
150
- counts = {"updated": 0, "skipped": 0, "error": 0}
151
-
152
- header("Patching Frontmatter")
153
- for skill_dir in sorted(skills_dir.iterdir()):
154
- if not skill_dir.is_dir():
155
- continue
156
- if args.skill and skill_dir.name != args.skill:
157
- continue
158
- skill_md = skill_dir / "SKILL.md"
159
- if not skill_md.exists():
160
- warn(f"{skill_dir.name} — no SKILL.md found")
161
- continue
162
- result = process_skill(skill_md, args.dry_run)
163
- counts[result] += 1
164
-
165
- print(f"\n{BOLD}━━━ Summary ━━━{RESET}")
166
- print(f" {GREEN}✅ Updated: {counts['updated']}{RESET}")
167
- print(f" {YELLOW}⏭️ Skipped: {counts['skipped']}{RESET}")
168
- if counts["error"]:
169
- print(f" {RED}❌ Errors: {counts['error']}{RESET}")
170
- if args.dry_run:
171
- print(f" {YELLOW}(dry-run — nothing written){RESET}")
172
-
173
- sys.exit(1 if counts["error"] > 0 else 0)
174
-
175
-
176
- if __name__ == "__main__":
177
- main()
1
+ #!/usr/bin/env python3
2
+ """
3
+ patch_skills_meta.py — Injects version/freshness metadata into SKILL.md frontmatter.
4
+
5
+ Adds the following fields to YAML frontmatter if missing:
6
+ version: 1.0.0
7
+ last-updated: 2026-03-12
8
+ applies-to-model: gemini-2.5-pro, claude-3-7-sonnet
9
+
10
+ Usage:
11
+ python .agent/scripts/patch_skills_meta.py .
12
+ python .agent/scripts/patch_skills_meta.py . --dry-run
13
+ python .agent/scripts/patch_skills_meta.py . --skill python-pro
14
+ """
15
+
16
+ import os
17
+ import sys
18
+ import argparse
19
+ import re
20
+ from pathlib import Path
21
+
22
+ RED = "\033[91m"
23
+ GREEN = "\033[92m"
24
+ YELLOW = "\033[93m"
25
+ BLUE = "\033[94m"
26
+ BOLD = "\033[1m"
27
+ RESET = "\033[0m"
28
+
29
+ META_FIELDS = {
30
+ "version": "1.0.0",
31
+ "last-updated": "2026-03-12",
32
+ "applies-to-model": "gemini-2.5-pro, claude-3-7-sonnet",
33
+ }
34
+
35
+
36
+ def header(title: str) -> None:
37
+ print(f"\n{BOLD}{BLUE}━━━ {title} ━━━{RESET}")
38
+
39
+
40
+ def ok(msg: str) -> None:
41
+ print(f" {GREEN}✅ {msg}{RESET}")
42
+
43
+
44
+ def skip(msg: str) -> None:
45
+ print(f" {YELLOW}⏭️ {msg}{RESET}")
46
+
47
+
48
+ def warn(msg: str) -> None:
49
+ print(f" {YELLOW}⚠️ {msg}{RESET}")
50
+
51
+
52
+ def fail(msg: str) -> None:
53
+ print(f" {RED}❌ {msg}{RESET}")
54
+
55
+
56
+ def patch_frontmatter(content: str) -> tuple[str, list[str]]:
57
+ """
58
+ Parse the YAML frontmatter block and inject missing meta fields.
59
+ Returns (patched_content, list_of_added_fields).
60
+ If no frontmatter is found, injects a minimal one.
61
+ """
62
+ added: list[str] = []
63
+
64
+ # Match frontmatter block: starts and ends with ---
65
+ fm_pattern = re.compile(r"^---\r?\n(.*?)\r?\n---", re.DOTALL)
66
+ match = fm_pattern.match(content)
67
+
68
+ if not match:
69
+ # No frontmatter — prepend a minimal block
70
+ new_fm_lines = ["---"]
71
+ for key, value in META_FIELDS.items():
72
+ new_fm_lines.append(f"{key}: {value}")
73
+ added.append(key)
74
+ new_fm_lines.append("---")
75
+ new_fm = "\n".join(new_fm_lines)
76
+ return new_fm + "\n\n" + content, added
77
+
78
+ fm_text = match.group(1)
79
+ fm_end = match.end()
80
+
81
+ # Parse existing lines preserving order
82
+ existing_keys = set()
83
+ for line in fm_text.splitlines():
84
+ m = re.match(r"^([a-zA-Z0-9_-]+)\s*:", line)
85
+ if m:
86
+ existing_keys.add(m.group(1))
87
+
88
+ # Build new frontmatter
89
+ new_fm_lines = fm_text.rstrip().splitlines()
90
+ for key, value in META_FIELDS.items():
91
+ if key not in existing_keys:
92
+ new_fm_lines.append(f"{key}: {value}")
93
+ added.append(key)
94
+
95
+ if not added:
96
+ return content, []
97
+
98
+ new_fm_block = "---\n" + "\n".join(new_fm_lines) + "\n---"
99
+ patched = new_fm_block + content[fm_end:]
100
+ return patched, added
101
+
102
+
103
+ def process_skill(skill_path: Path, dry_run: bool) -> str:
104
+ """Process a single SKILL.md. Returns 'updated', 'skipped', or 'error'."""
105
+ try:
106
+ content = skill_path.read_text(encoding="utf-8")
107
+ patched, added = patch_frontmatter(content)
108
+
109
+ skill_name = skill_path.parent.name
110
+
111
+ if not added:
112
+ skip(f"{skill_name} — all meta fields present")
113
+ return "skipped"
114
+
115
+ field_list = ", ".join(added)
116
+ if dry_run:
117
+ warn(f"[DRY RUN] {skill_name} — would add: {field_list}")
118
+ return "updated"
119
+
120
+ skill_path.write_text(patched, encoding="utf-8")
121
+ ok(f"{skill_name} — added: {field_list}")
122
+ return "updated"
123
+
124
+ except Exception as e:
125
+ fail(f"{skill_path.parent.name} — {e}")
126
+ return "error"
127
+
128
+
129
+ def main() -> None:
130
+ parser = argparse.ArgumentParser(
131
+ description="Injects version/freshness metadata into SKILL.md frontmatter"
132
+ )
133
+ parser.add_argument("path", help="Project root directory")
134
+ parser.add_argument("--dry-run", action="store_true", help="Show changes without writing")
135
+ parser.add_argument("--skill", help="Only patch a specific skill by name")
136
+ args = parser.parse_args()
137
+
138
+ project_root = Path(args.path).resolve()
139
+ skills_dir = project_root / ".agent" / "skills"
140
+
141
+ if not skills_dir.is_dir():
142
+ fail(f"Skills directory not found: {skills_dir}")
143
+ sys.exit(1)
144
+
145
+ print(f"{BOLD}Tribunal — patch_skills_meta.py{RESET}")
146
+ if args.dry_run:
147
+ print(f" {YELLOW}DRY RUN — no files will be written{RESET}")
148
+ print(f"Skills dir: {skills_dir}\n")
149
+
150
+ counts = {"updated": 0, "skipped": 0, "error": 0}
151
+
152
+ header("Patching Frontmatter")
153
+ for skill_dir in sorted(skills_dir.iterdir()):
154
+ if not skill_dir.is_dir():
155
+ continue
156
+ if args.skill and skill_dir.name != args.skill:
157
+ continue
158
+ skill_md = skill_dir / "SKILL.md"
159
+ if not skill_md.exists():
160
+ warn(f"{skill_dir.name} — no SKILL.md found")
161
+ continue
162
+ result = process_skill(skill_md, args.dry_run)
163
+ counts[result] += 1
164
+
165
+ print(f"\n{BOLD}━━━ Summary ━━━{RESET}")
166
+ print(f" {GREEN}✅ Updated: {counts['updated']}{RESET}")
167
+ print(f" {YELLOW}⏭️ Skipped: {counts['skipped']}{RESET}")
168
+ if counts["error"]:
169
+ print(f" {RED}❌ Errors: {counts['error']}{RESET}")
170
+ if args.dry_run:
171
+ print(f" {YELLOW}(dry-run — nothing written){RESET}")
172
+
173
+ sys.exit(1 if counts["error"] > 0 else 0)
174
+
175
+
176
+ if __name__ == "__main__":
177
+ main()