tribunal-kit 1.0.0 → 2.4.2
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.
- package/.agent/.shared/ui-ux-pro-max/README.md +3 -3
- package/.agent/ARCHITECTURE.md +205 -10
- package/.agent/GEMINI.md +37 -7
- package/.agent/agents/accessibility-reviewer.md +134 -0
- package/.agent/agents/ai-code-reviewer.md +129 -0
- package/.agent/agents/frontend-specialist.md +3 -0
- package/.agent/agents/game-developer.md +21 -21
- package/.agent/agents/logic-reviewer.md +12 -0
- package/.agent/agents/mobile-reviewer.md +79 -0
- package/.agent/agents/orchestrator.md +56 -26
- package/.agent/agents/performance-reviewer.md +36 -0
- package/.agent/agents/supervisor-agent.md +156 -0
- package/.agent/agents/swarm-worker-contracts.md +166 -0
- package/.agent/agents/swarm-worker-registry.md +92 -0
- package/.agent/rules/GEMINI.md +134 -5
- package/.agent/scripts/bundle_analyzer.py +259 -0
- package/.agent/scripts/dependency_analyzer.py +247 -0
- package/.agent/scripts/lint_runner.py +188 -0
- package/.agent/scripts/patch_skills_meta.py +177 -0
- package/.agent/scripts/patch_skills_output.py +285 -0
- package/.agent/scripts/schema_validator.py +279 -0
- package/.agent/scripts/security_scan.py +224 -0
- package/.agent/scripts/session_manager.py +144 -3
- package/.agent/scripts/skill_integrator.py +234 -0
- package/.agent/scripts/strengthen_skills.py +220 -0
- package/.agent/scripts/swarm_dispatcher.py +317 -0
- package/.agent/scripts/test_runner.py +192 -0
- package/.agent/scripts/test_swarm_dispatcher.py +163 -0
- package/.agent/skills/agent-organizer/SKILL.md +132 -0
- package/.agent/skills/agentic-patterns/SKILL.md +335 -0
- package/.agent/skills/api-patterns/SKILL.md +226 -50
- package/.agent/skills/app-builder/SKILL.md +215 -52
- package/.agent/skills/architecture/SKILL.md +176 -31
- package/.agent/skills/bash-linux/SKILL.md +150 -134
- package/.agent/skills/behavioral-modes/SKILL.md +152 -160
- package/.agent/skills/brainstorming/SKILL.md +148 -101
- package/.agent/skills/brainstorming/dynamic-questioning.md +10 -0
- package/.agent/skills/clean-code/SKILL.md +139 -134
- package/.agent/skills/code-review-checklist/SKILL.md +177 -80
- package/.agent/skills/config-validator/SKILL.md +165 -0
- package/.agent/skills/csharp-developer/SKILL.md +107 -0
- package/.agent/skills/database-design/SKILL.md +252 -29
- package/.agent/skills/deployment-procedures/SKILL.md +122 -175
- package/.agent/skills/devops-engineer/SKILL.md +134 -0
- package/.agent/skills/devops-incident-responder/SKILL.md +98 -0
- package/.agent/skills/documentation-templates/SKILL.md +175 -121
- package/.agent/skills/dotnet-core-expert/SKILL.md +103 -0
- package/.agent/skills/edge-computing/SKILL.md +213 -0
- package/.agent/skills/frontend-design/SKILL.md +76 -0
- package/.agent/skills/frontend-design/color-system.md +18 -0
- package/.agent/skills/frontend-design/typography-system.md +18 -0
- package/.agent/skills/game-development/SKILL.md +69 -0
- package/.agent/skills/geo-fundamentals/SKILL.md +158 -99
- package/.agent/skills/github-operations/SKILL.md +354 -0
- package/.agent/skills/i18n-localization/SKILL.md +158 -96
- package/.agent/skills/intelligent-routing/SKILL.md +89 -285
- package/.agent/skills/intelligent-routing/router-manifest.md +65 -0
- package/.agent/skills/lint-and-validate/SKILL.md +229 -27
- package/.agent/skills/llm-engineering/SKILL.md +258 -0
- package/.agent/skills/local-first/SKILL.md +203 -0
- package/.agent/skills/mcp-builder/SKILL.md +159 -111
- package/.agent/skills/mobile-design/SKILL.md +102 -282
- package/.agent/skills/nextjs-react-expert/SKILL.md +143 -227
- package/.agent/skills/nodejs-best-practices/SKILL.md +201 -254
- package/.agent/skills/observability/SKILL.md +285 -0
- package/.agent/skills/parallel-agents/SKILL.md +124 -118
- package/.agent/skills/performance-profiling/SKILL.md +143 -89
- package/.agent/skills/plan-writing/SKILL.md +133 -97
- package/.agent/skills/platform-engineer/SKILL.md +135 -0
- package/.agent/skills/powershell-windows/SKILL.md +167 -104
- package/.agent/skills/python-patterns/SKILL.md +149 -361
- package/.agent/skills/python-pro/SKILL.md +114 -0
- package/.agent/skills/react-specialist/SKILL.md +107 -0
- package/.agent/skills/readme-builder/SKILL.md +270 -0
- package/.agent/skills/realtime-patterns/SKILL.md +296 -0
- package/.agent/skills/red-team-tactics/SKILL.md +136 -134
- package/.agent/skills/rust-pro/SKILL.md +237 -173
- package/.agent/skills/seo-fundamentals/SKILL.md +134 -82
- package/.agent/skills/server-management/SKILL.md +155 -104
- package/.agent/skills/sql-pro/SKILL.md +104 -0
- package/.agent/skills/systematic-debugging/SKILL.md +156 -79
- package/.agent/skills/tailwind-patterns/SKILL.md +163 -205
- package/.agent/skills/tdd-workflow/SKILL.md +148 -88
- package/.agent/skills/test-result-analyzer/SKILL.md +299 -0
- package/.agent/skills/testing-patterns/SKILL.md +141 -114
- package/.agent/skills/trend-researcher/SKILL.md +228 -0
- package/.agent/skills/ui-ux-pro-max/SKILL.md +107 -0
- package/.agent/skills/ui-ux-researcher/SKILL.md +234 -0
- package/.agent/skills/vue-expert/SKILL.md +118 -0
- package/.agent/skills/vulnerability-scanner/SKILL.md +228 -188
- package/.agent/skills/web-design-guidelines/SKILL.md +148 -33
- package/.agent/skills/webapp-testing/SKILL.md +171 -122
- package/.agent/skills/whimsy-injector/SKILL.md +349 -0
- package/.agent/skills/workflow-optimizer/SKILL.md +219 -0
- package/.agent/workflows/api-tester.md +279 -0
- package/.agent/workflows/audit.md +168 -0
- package/.agent/workflows/brainstorm.md +65 -19
- package/.agent/workflows/changelog.md +144 -0
- package/.agent/workflows/create.md +67 -14
- package/.agent/workflows/debug.md +122 -30
- package/.agent/workflows/deploy.md +82 -31
- package/.agent/workflows/enhance.md +59 -27
- package/.agent/workflows/fix.md +143 -0
- package/.agent/workflows/generate.md +84 -20
- package/.agent/workflows/migrate.md +163 -0
- package/.agent/workflows/orchestrate.md +66 -17
- package/.agent/workflows/performance-benchmarker.md +305 -0
- package/.agent/workflows/plan.md +76 -33
- package/.agent/workflows/preview.md +73 -17
- package/.agent/workflows/refactor.md +153 -0
- package/.agent/workflows/review-ai.md +140 -0
- package/.agent/workflows/review.md +83 -16
- package/.agent/workflows/session.md +154 -0
- package/.agent/workflows/status.md +74 -18
- package/.agent/workflows/strengthen-skills.md +99 -0
- package/.agent/workflows/swarm.md +194 -0
- package/.agent/workflows/test.md +80 -31
- package/.agent/workflows/tribunal-backend.md +55 -13
- package/.agent/workflows/tribunal-database.md +62 -18
- package/.agent/workflows/tribunal-frontend.md +58 -12
- package/.agent/workflows/tribunal-full.md +70 -11
- package/.agent/workflows/tribunal-mobile.md +123 -0
- package/.agent/workflows/tribunal-performance.md +152 -0
- package/.agent/workflows/ui-ux-pro-max.md +100 -82
- package/README.md +117 -62
- package/bin/tribunal-kit.js +542 -288
- package/package.json +10 -6
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
lint_runner.py — Standalone lint runner for the Tribunal Agent Kit.
|
|
4
|
+
|
|
5
|
+
Detects and runs available linters in the project:
|
|
6
|
+
- ESLint (JS/TS)
|
|
7
|
+
- Prettier (formatting)
|
|
8
|
+
- Ruff / Flake8 (Python)
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
python .agent/scripts/lint_runner.py .
|
|
12
|
+
python .agent/scripts/lint_runner.py . --fix
|
|
13
|
+
python .agent/scripts/lint_runner.py . --files src/index.ts src/utils.ts
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
import sys
|
|
18
|
+
import subprocess
|
|
19
|
+
import argparse
|
|
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
|
+
|
|
30
|
+
def header(title: str) -> None:
|
|
31
|
+
print(f"\n{BOLD}{BLUE}━━━ {title} ━━━{RESET}")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def ok(msg: str) -> None:
|
|
35
|
+
print(f" {GREEN}✅ {msg}{RESET}")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def fail(msg: str) -> None:
|
|
39
|
+
print(f" {RED}❌ {msg}{RESET}")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def skip(msg: str) -> None:
|
|
43
|
+
print(f" {YELLOW}⏭️ {msg}{RESET}")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def run_linter(label: str, cmd: list[str], cwd: str) -> bool:
|
|
47
|
+
"""Run a linter command and return True if it passes."""
|
|
48
|
+
try:
|
|
49
|
+
result = subprocess.run(
|
|
50
|
+
cmd, cwd=cwd, capture_output=True, text=True, timeout=120
|
|
51
|
+
)
|
|
52
|
+
if result.returncode == 0:
|
|
53
|
+
ok(f"{label} — clean")
|
|
54
|
+
return True
|
|
55
|
+
fail(f"{label} — issues found")
|
|
56
|
+
output = (result.stdout + result.stderr).strip()
|
|
57
|
+
if output:
|
|
58
|
+
for line in output.split("\n")[:15]:
|
|
59
|
+
print(f" {line}")
|
|
60
|
+
total_lines = len(output.split("\n"))
|
|
61
|
+
if total_lines > 15:
|
|
62
|
+
print(f" ... and {total_lines - 15} more lines")
|
|
63
|
+
return False
|
|
64
|
+
except FileNotFoundError:
|
|
65
|
+
skip(f"{label} — tool not installed")
|
|
66
|
+
return True
|
|
67
|
+
except subprocess.TimeoutExpired:
|
|
68
|
+
fail(f"{label} — timed out after 120s")
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def detect_linters(project_root: str) -> dict[str, bool]:
|
|
73
|
+
"""Detect which linters are available in the project."""
|
|
74
|
+
root = Path(project_root)
|
|
75
|
+
available: dict[str, bool] = {}
|
|
76
|
+
|
|
77
|
+
# JS/TS linters
|
|
78
|
+
pkg_json = root / "package.json"
|
|
79
|
+
if pkg_json.exists():
|
|
80
|
+
available["eslint"] = any(
|
|
81
|
+
(root / f).exists()
|
|
82
|
+
for f in [".eslintrc", ".eslintrc.js", ".eslintrc.json", ".eslintrc.yml", "eslint.config.js", "eslint.config.mjs"]
|
|
83
|
+
) or pkg_json.exists() # ESLint can be configured in package.json too
|
|
84
|
+
available["prettier"] = any(
|
|
85
|
+
(root / f).exists()
|
|
86
|
+
for f in [".prettierrc", ".prettierrc.js", ".prettierrc.json", "prettier.config.js"]
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Python linters
|
|
90
|
+
available["ruff"] = (root / "pyproject.toml").exists() or (root / "ruff.toml").exists()
|
|
91
|
+
available["flake8"] = (root / ".flake8").exists() or (root / "setup.cfg").exists()
|
|
92
|
+
|
|
93
|
+
return available
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def main() -> None:
|
|
97
|
+
parser = argparse.ArgumentParser(
|
|
98
|
+
description="Tribunal lint runner — detects and runs available linters"
|
|
99
|
+
)
|
|
100
|
+
parser.add_argument("path", help="Project root directory")
|
|
101
|
+
parser.add_argument("--fix", action="store_true", help="Auto-fix issues where supported")
|
|
102
|
+
parser.add_argument("--files", nargs="*", help="Specific files to lint (instead of entire project)")
|
|
103
|
+
args = parser.parse_args()
|
|
104
|
+
|
|
105
|
+
project_root = os.path.abspath(args.path)
|
|
106
|
+
if not os.path.isdir(project_root):
|
|
107
|
+
fail(f"Directory not found: {project_root}")
|
|
108
|
+
sys.exit(1)
|
|
109
|
+
|
|
110
|
+
print(f"{BOLD}Tribunal — lint_runner.py{RESET}")
|
|
111
|
+
print(f"Project: {project_root}")
|
|
112
|
+
|
|
113
|
+
available = detect_linters(project_root)
|
|
114
|
+
if not any(available.values()):
|
|
115
|
+
skip("No linter configuration detected in this project")
|
|
116
|
+
sys.exit(0)
|
|
117
|
+
|
|
118
|
+
failures = 0
|
|
119
|
+
file_args = args.files or []
|
|
120
|
+
|
|
121
|
+
# ESLint
|
|
122
|
+
if available.get("eslint"):
|
|
123
|
+
header("ESLint")
|
|
124
|
+
cmd = ["npx", "eslint"]
|
|
125
|
+
if args.fix:
|
|
126
|
+
cmd.append("--fix")
|
|
127
|
+
if file_args:
|
|
128
|
+
cmd.extend(file_args)
|
|
129
|
+
else:
|
|
130
|
+
cmd.extend([".", "--max-warnings=0"])
|
|
131
|
+
if not run_linter("ESLint", cmd, project_root):
|
|
132
|
+
failures += 1
|
|
133
|
+
|
|
134
|
+
# Prettier
|
|
135
|
+
if available.get("prettier"):
|
|
136
|
+
header("Prettier")
|
|
137
|
+
cmd = ["npx", "prettier"]
|
|
138
|
+
if args.fix:
|
|
139
|
+
cmd.extend(["--write", "."])
|
|
140
|
+
else:
|
|
141
|
+
cmd.extend(["--check", "."])
|
|
142
|
+
if file_args:
|
|
143
|
+
cmd = ["npx", "prettier", "--write" if args.fix else "--check"] + file_args
|
|
144
|
+
if not run_linter("Prettier", cmd, project_root):
|
|
145
|
+
failures += 1
|
|
146
|
+
|
|
147
|
+
# Ruff (Python)
|
|
148
|
+
if available.get("ruff"):
|
|
149
|
+
header("Ruff (Python)")
|
|
150
|
+
cmd = ["ruff", "check"]
|
|
151
|
+
if args.fix:
|
|
152
|
+
cmd.append("--fix")
|
|
153
|
+
if file_args:
|
|
154
|
+
cmd.extend(file_args)
|
|
155
|
+
else:
|
|
156
|
+
cmd.append(".")
|
|
157
|
+
if not run_linter("Ruff", cmd, project_root):
|
|
158
|
+
failures += 1
|
|
159
|
+
|
|
160
|
+
# Flake8 (Python fallback)
|
|
161
|
+
if available.get("flake8") and not available.get("ruff"):
|
|
162
|
+
header("Flake8 (Python)")
|
|
163
|
+
cmd = ["flake8"]
|
|
164
|
+
if file_args:
|
|
165
|
+
cmd.extend(file_args)
|
|
166
|
+
else:
|
|
167
|
+
cmd.append(".")
|
|
168
|
+
if not run_linter("Flake8", cmd, project_root):
|
|
169
|
+
failures += 1
|
|
170
|
+
|
|
171
|
+
# TypeScript type check (always run if package.json exists)
|
|
172
|
+
if Path(project_root, "package.json").exists():
|
|
173
|
+
header("TypeScript")
|
|
174
|
+
if not run_linter("tsc --noEmit", ["npx", "tsc", "--noEmit"], project_root):
|
|
175
|
+
failures += 1
|
|
176
|
+
|
|
177
|
+
# Summary
|
|
178
|
+
print(f"\n{BOLD}━━━ Lint Summary ━━━{RESET}")
|
|
179
|
+
if failures == 0:
|
|
180
|
+
ok("All linters passed")
|
|
181
|
+
else:
|
|
182
|
+
fail(f"{failures} linter(s) reported issues")
|
|
183
|
+
|
|
184
|
+
sys.exit(1 if failures > 0 else 0)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
if __name__ == "__main__":
|
|
188
|
+
main()
|
|
@@ -0,0 +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()
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
patch_skills_output.py — Adds structured Output Format sections to SKILL.md files.
|
|
4
|
+
|
|
5
|
+
Inserts a domain-tailored '## Output Format' block before the Tribunal Integration
|
|
6
|
+
section (or appends to the end if no Tribunal section is present).
|
|
7
|
+
|
|
8
|
+
Skills already containing '## Output Format', '## Output', or '## Report Format'
|
|
9
|
+
are skipped automatically.
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
python .agent/scripts/patch_skills_output.py .
|
|
13
|
+
python .agent/scripts/patch_skills_output.py . --dry-run
|
|
14
|
+
python .agent/scripts/patch_skills_output.py . --skill python-pro
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
import sys
|
|
19
|
+
import re
|
|
20
|
+
import argparse
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
RED = "\033[91m"
|
|
24
|
+
GREEN = "\033[92m"
|
|
25
|
+
YELLOW = "\033[93m"
|
|
26
|
+
BLUE = "\033[94m"
|
|
27
|
+
BOLD = "\033[1m"
|
|
28
|
+
RESET = "\033[0m"
|
|
29
|
+
|
|
30
|
+
# ─── Templates ───────────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
CODE_QUALITY_TEMPLATE = """\
|
|
33
|
+
## Output Format
|
|
34
|
+
|
|
35
|
+
When this skill produces or reviews code, structure your output as follows:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
━━━ {skill_name} Report ━━━━━━━━━━━━━━━━━━━━━━━━
|
|
39
|
+
Skill: {skill_name}
|
|
40
|
+
Language: [detected language / framework]
|
|
41
|
+
Scope: [N files · N functions]
|
|
42
|
+
─────────────────────────────────────────────────
|
|
43
|
+
✅ Passed: [checks that passed, or "All clean"]
|
|
44
|
+
⚠️ Warnings: [non-blocking issues, or "None"]
|
|
45
|
+
❌ Blocked: [blocking issues requiring fix, or "None"]
|
|
46
|
+
─────────────────────────────────────────────────
|
|
47
|
+
VBC status: PENDING → VERIFIED
|
|
48
|
+
Evidence: [test output / lint pass / compile success]
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**VBC (Verification-Before-Completion) is mandatory.**
|
|
52
|
+
Do not mark status as VERIFIED until concrete terminal evidence is provided.
|
|
53
|
+
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
DECISION_CARD_TEMPLATE = """\
|
|
57
|
+
## Output Format
|
|
58
|
+
|
|
59
|
+
When this skill produces a recommendation or design decision, structure your output as:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
━━━ {skill_name} Recommendation ━━━━━━━━━━━━━━━━
|
|
63
|
+
Decision: [what was chosen / proposed]
|
|
64
|
+
Rationale: [why — one concise line]
|
|
65
|
+
Trade-offs: [what is consciously accepted]
|
|
66
|
+
Next action: [concrete next step for the user]
|
|
67
|
+
─────────────────────────────────────────────────
|
|
68
|
+
Pre-Flight: ✅ All checks passed
|
|
69
|
+
or ❌ [blocking item that must be resolved first]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
GENERIC_TEMPLATE = """\
|
|
75
|
+
## Output Format
|
|
76
|
+
|
|
77
|
+
When this skill completes a task, structure your output as:
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
━━━ {skill_name} Output ━━━━━━━━━━━━━━━━━━━━━━━━
|
|
81
|
+
Task: [what was performed]
|
|
82
|
+
Result: [outcome summary — one line]
|
|
83
|
+
─────────────────────────────────────────────────
|
|
84
|
+
Checks: ✅ [N passed] · ⚠️ [N warnings] · ❌ [N blocked]
|
|
85
|
+
VBC status: PENDING → VERIFIED
|
|
86
|
+
Evidence: [link to terminal output, test result, or file diff]
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
# ─── Skill → template routing ────────────────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
CODE_GEN_SKILLS = {
|
|
94
|
+
"python-pro", "clean-code", "dotnet-core-expert", "rust-pro",
|
|
95
|
+
"nextjs-react-expert", "vue-expert", "react-specialist",
|
|
96
|
+
"csharp-developer", "nodejs-best-practices", "python-patterns",
|
|
97
|
+
"tailwind-patterns", "bash-linux", "powershell-windows",
|
|
98
|
+
"llm-engineering", "mcp-builder", "game-development",
|
|
99
|
+
"edge-computing", "local-first", "realtime-patterns",
|
|
100
|
+
"tdd-workflow", "testing-patterns", "lint-and-validate",
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
DECISION_SKILLS = {
|
|
104
|
+
"api-patterns", "database-design", "architecture", "observability",
|
|
105
|
+
"devops-engineer", "platform-engineer", "deployment-procedures",
|
|
106
|
+
"server-management", "security-auditor", "vulnerability-scanner",
|
|
107
|
+
"red-team-tactics", "performance-profiling", "i18n-localization",
|
|
108
|
+
"geo-fundamentals", "seo-fundamentals", "sql-pro",
|
|
109
|
+
"brainstorming", "plan-writing", "behavioral-modes",
|
|
110
|
+
"app-builder", "intelligent-routing", "mobile-design",
|
|
111
|
+
"frontend-design", "ui-ux-pro-max", "ui-ux-researcher",
|
|
112
|
+
"web-design-guidelines", "trend-researcher",
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# Skills that already have output format sections — SKIP
|
|
116
|
+
ALREADY_HAVE_OUTPUT = {
|
|
117
|
+
"whimsy-injector", "workflow-optimizer",
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
# Markers indicating an existing output section
|
|
121
|
+
EXISTING_OUTPUT_MARKERS = [
|
|
122
|
+
"## Output Format",
|
|
123
|
+
"## Output\n",
|
|
124
|
+
"## Report Format",
|
|
125
|
+
"## Output Card",
|
|
126
|
+
"Whimsy Injection Report",
|
|
127
|
+
"Workflow Optimization Report",
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
# ─── Logic ───────────────────────────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
def get_template(skill_name: str) -> str:
|
|
133
|
+
if skill_name in CODE_GEN_SKILLS:
|
|
134
|
+
return CODE_QUALITY_TEMPLATE
|
|
135
|
+
if skill_name in DECISION_SKILLS:
|
|
136
|
+
return DECISION_CARD_TEMPLATE
|
|
137
|
+
return GENERIC_TEMPLATE
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def has_output_section(content: str) -> bool:
|
|
141
|
+
for marker in EXISTING_OUTPUT_MARKERS:
|
|
142
|
+
if marker in content:
|
|
143
|
+
return True
|
|
144
|
+
return False
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def get_skill_name_from_frontmatter(content: str) -> str | None:
|
|
148
|
+
"""Extract the 'name:' field from YAML frontmatter."""
|
|
149
|
+
match = re.search(r"^name:\s*(.+)$", content, re.MULTILINE)
|
|
150
|
+
return match.group(1).strip() if match else None
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def build_block(template: str, skill_name: str) -> str:
|
|
154
|
+
# Capitalise display name
|
|
155
|
+
display = skill_name.replace("-", " ").title()
|
|
156
|
+
return template.replace("{skill_name}", display)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def inject_output_block(content: str, block: str) -> str:
|
|
160
|
+
"""
|
|
161
|
+
Insert block before '## 🏛️ Tribunal Integration' if present,
|
|
162
|
+
otherwise append to end of file.
|
|
163
|
+
"""
|
|
164
|
+
tribunal_markers = [
|
|
165
|
+
"## 🏛️ Tribunal Integration",
|
|
166
|
+
"## Tribunal Integration",
|
|
167
|
+
]
|
|
168
|
+
for marker in tribunal_markers:
|
|
169
|
+
idx = content.find(marker)
|
|
170
|
+
if idx != -1:
|
|
171
|
+
return content[:idx] + block + "\n---\n\n" + content[idx:]
|
|
172
|
+
|
|
173
|
+
# Append to end with a separator
|
|
174
|
+
return content.rstrip() + "\n\n---\n\n" + block
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def process_skill(skill_dir: Path, dry_run: bool) -> str:
|
|
178
|
+
"""Returns 'updated', 'skipped', or 'error'."""
|
|
179
|
+
skill_name = skill_dir.name
|
|
180
|
+
skill_md = skill_dir / "SKILL.md"
|
|
181
|
+
|
|
182
|
+
if not skill_md.exists():
|
|
183
|
+
return "skipped"
|
|
184
|
+
|
|
185
|
+
try:
|
|
186
|
+
content = skill_md.read_text(encoding="utf-8")
|
|
187
|
+
|
|
188
|
+
if skill_name in ALREADY_HAVE_OUTPUT or has_output_section(content):
|
|
189
|
+
skip(f"{skill_name} — output format already present")
|
|
190
|
+
return "skipped"
|
|
191
|
+
|
|
192
|
+
# Determine display name
|
|
193
|
+
display_name = get_skill_name_from_frontmatter(content) or skill_name
|
|
194
|
+
template = get_template(skill_name)
|
|
195
|
+
block = build_block(template, display_name)
|
|
196
|
+
patched = inject_output_block(content, block)
|
|
197
|
+
|
|
198
|
+
if dry_run:
|
|
199
|
+
template_type = (
|
|
200
|
+
"Code Quality" if skill_name in CODE_GEN_SKILLS
|
|
201
|
+
else "Decision Card" if skill_name in DECISION_SKILLS
|
|
202
|
+
else "Generic"
|
|
203
|
+
)
|
|
204
|
+
warn(f"[DRY RUN] {skill_name} — would add Output Format ({template_type})")
|
|
205
|
+
return "updated"
|
|
206
|
+
|
|
207
|
+
skill_md.write_text(patched, encoding="utf-8")
|
|
208
|
+
template_type = (
|
|
209
|
+
"Code Quality" if skill_name in CODE_GEN_SKILLS
|
|
210
|
+
else "Decision Card" if skill_name in DECISION_SKILLS
|
|
211
|
+
else "Generic"
|
|
212
|
+
)
|
|
213
|
+
ok(f"{skill_name} — added Output Format ({template_type})")
|
|
214
|
+
return "updated"
|
|
215
|
+
|
|
216
|
+
except Exception as e:
|
|
217
|
+
fail(f"{skill_name} — {e}")
|
|
218
|
+
return "error"
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def header(title: str) -> None:
|
|
222
|
+
print(f"\n{BOLD}{BLUE}━━━ {title} ━━━{RESET}")
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def ok(msg: str) -> None:
|
|
226
|
+
print(f" {GREEN}✅ {msg}{RESET}")
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def skip(msg: str) -> None:
|
|
230
|
+
print(f" {YELLOW}⏭️ {msg}{RESET}")
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def warn(msg: str) -> None:
|
|
234
|
+
print(f" {YELLOW}⚠️ {msg}{RESET}")
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def fail(msg: str) -> None:
|
|
238
|
+
print(f" {RED}❌ {msg}{RESET}")
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def main() -> None:
|
|
242
|
+
parser = argparse.ArgumentParser(
|
|
243
|
+
description="Adds Output Format sections to SKILL.md files that are missing them"
|
|
244
|
+
)
|
|
245
|
+
parser.add_argument("path", help="Project root directory")
|
|
246
|
+
parser.add_argument("--dry-run", action="store_true", help="Show changes without writing")
|
|
247
|
+
parser.add_argument("--skill", help="Only patch a specific skill by name")
|
|
248
|
+
args = parser.parse_args()
|
|
249
|
+
|
|
250
|
+
project_root = Path(args.path).resolve()
|
|
251
|
+
skills_dir = project_root / ".agent" / "skills"
|
|
252
|
+
|
|
253
|
+
if not skills_dir.is_dir():
|
|
254
|
+
fail(f"Skills directory not found: {skills_dir}")
|
|
255
|
+
sys.exit(1)
|
|
256
|
+
|
|
257
|
+
print(f"{BOLD}Tribunal — patch_skills_output.py{RESET}")
|
|
258
|
+
if args.dry_run:
|
|
259
|
+
print(f" {YELLOW}DRY RUN — no files will be written{RESET}")
|
|
260
|
+
print(f"Skills dir: {skills_dir}\n")
|
|
261
|
+
|
|
262
|
+
counts: dict[str, int] = {"updated": 0, "skipped": 0, "error": 0}
|
|
263
|
+
|
|
264
|
+
header("Patching Output Format Sections")
|
|
265
|
+
for skill_dir in sorted(skills_dir.iterdir()):
|
|
266
|
+
if not skill_dir.is_dir():
|
|
267
|
+
continue
|
|
268
|
+
if args.skill and skill_dir.name != args.skill:
|
|
269
|
+
continue
|
|
270
|
+
result = process_skill(skill_dir, args.dry_run)
|
|
271
|
+
counts[result] += 1
|
|
272
|
+
|
|
273
|
+
print(f"\n{BOLD}━━━ Summary ━━━{RESET}")
|
|
274
|
+
print(f" {GREEN}✅ Updated: {counts['updated']}{RESET}")
|
|
275
|
+
print(f" {YELLOW}⏭️ Skipped: {counts['skipped']}{RESET}")
|
|
276
|
+
if counts["error"]:
|
|
277
|
+
print(f" {RED}❌ Errors: {counts['error']}{RESET}")
|
|
278
|
+
if args.dry_run:
|
|
279
|
+
print(f" {YELLOW}(dry-run — nothing written){RESET}")
|
|
280
|
+
|
|
281
|
+
sys.exit(1 if counts["error"] > 0 else 0)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
if __name__ == "__main__":
|
|
285
|
+
main()
|