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
|
@@ -7,6 +7,10 @@ Usage:
|
|
|
7
7
|
python .agent/scripts/session_manager.py load
|
|
8
8
|
python .agent/scripts/session_manager.py show
|
|
9
9
|
python .agent/scripts/session_manager.py clear
|
|
10
|
+
python .agent/scripts/session_manager.py status
|
|
11
|
+
python .agent/scripts/session_manager.py tag <label>
|
|
12
|
+
python .agent/scripts/session_manager.py list [--all]
|
|
13
|
+
python .agent/scripts/session_manager.py export [--stdout]
|
|
10
14
|
"""
|
|
11
15
|
|
|
12
16
|
import os
|
|
@@ -20,11 +24,13 @@ STATE_FILE = ".agent_session.json"
|
|
|
20
24
|
GREEN = "\033[92m"
|
|
21
25
|
YELLOW = "\033[93m"
|
|
22
26
|
BLUE = "\033[94m"
|
|
27
|
+
CYAN = "\033[96m"
|
|
23
28
|
RED = "\033[91m"
|
|
24
29
|
BOLD = "\033[1m"
|
|
25
30
|
RESET = "\033[0m"
|
|
26
31
|
|
|
27
|
-
VALID_COMMANDS = {"save", "load", "show", "clear"}
|
|
32
|
+
VALID_COMMANDS = {"save", "load", "show", "clear", "status", "tag", "list", "export"}
|
|
33
|
+
LIST_PAGE_SIZE = 10
|
|
28
34
|
|
|
29
35
|
|
|
30
36
|
def load_state() -> dict:
|
|
@@ -49,6 +55,7 @@ def cmd_save(note: str) -> None:
|
|
|
49
55
|
"timestamp": datetime.now().isoformat(),
|
|
50
56
|
"note": note,
|
|
51
57
|
"session": len(state.get("history", [])) + 1,
|
|
58
|
+
"tags": [],
|
|
52
59
|
}
|
|
53
60
|
state.setdefault("history", []).append(entry)
|
|
54
61
|
state["current"] = entry
|
|
@@ -64,10 +71,12 @@ def cmd_load() -> None:
|
|
|
64
71
|
if not current:
|
|
65
72
|
print(f"{YELLOW}No active session — use 'save' first.{RESET}")
|
|
66
73
|
return
|
|
74
|
+
tags_str = (", ".join(current.get("tags", []))) or "none"
|
|
67
75
|
print(f"{BOLD}Current session:{RESET}")
|
|
68
76
|
print(f" Session: #{current['session']}")
|
|
69
77
|
print(f" Time: {current['timestamp']}")
|
|
70
78
|
print(f" Note: {current['note']}")
|
|
79
|
+
print(f" Tags: {tags_str}")
|
|
71
80
|
|
|
72
81
|
|
|
73
82
|
def cmd_show() -> None:
|
|
@@ -78,7 +87,9 @@ def cmd_show() -> None:
|
|
|
78
87
|
return
|
|
79
88
|
print(f"{BOLD}Session History ({len(history)} total):{RESET}")
|
|
80
89
|
for entry in reversed(history[-10:]):
|
|
81
|
-
|
|
90
|
+
tags_str = (", ".join(entry.get("tags", []))) or ""
|
|
91
|
+
tags_display = f" [{tags_str}]" if tags_str else ""
|
|
92
|
+
print(f"\n {BLUE}#{entry['session']}{RESET} — {entry['timestamp'][:16]}{tags_display}")
|
|
82
93
|
print(f" {entry['note']}")
|
|
83
94
|
|
|
84
95
|
|
|
@@ -91,9 +102,128 @@ def cmd_clear() -> None:
|
|
|
91
102
|
print(f"{YELLOW}No session file found — nothing to clear.{RESET}")
|
|
92
103
|
|
|
93
104
|
|
|
105
|
+
def cmd_status() -> None:
|
|
106
|
+
"""Print a compact status summary of the last 3 sessions."""
|
|
107
|
+
state = load_state()
|
|
108
|
+
history = state.get("history", [])
|
|
109
|
+
current = state.get("current")
|
|
110
|
+
|
|
111
|
+
if not history:
|
|
112
|
+
print(f"{YELLOW}No session history — use 'save' to start tracking.{RESET}")
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
total = len(history)
|
|
116
|
+
recent = history[-3:]
|
|
117
|
+
|
|
118
|
+
print(f"\n{BOLD}{CYAN}━━━ Session Status ━━━━━━━━━━━━━━━━━━━━━━━━{RESET}")
|
|
119
|
+
print(f" Total sessions: {total}")
|
|
120
|
+
if current:
|
|
121
|
+
print(f" Active: #{current['session']} — {current['note'][:60]}")
|
|
122
|
+
print(f"\n{BOLD} Last 3 sessions:{RESET}")
|
|
123
|
+
for entry in reversed(recent):
|
|
124
|
+
tags_str = (", ".join(entry.get("tags", []))) or ""
|
|
125
|
+
tags_display = f" [{tags_str}]" if tags_str else ""
|
|
126
|
+
ts = entry["timestamp"][:16]
|
|
127
|
+
print(f" {BLUE}#{entry['session']}{RESET} {ts}{tags_display}")
|
|
128
|
+
print(f" {entry['note'][:70]}")
|
|
129
|
+
print(f"{CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{RESET}\n")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def cmd_tag(label: str) -> None:
|
|
133
|
+
"""Add a tag label to the current session entry."""
|
|
134
|
+
if not label:
|
|
135
|
+
print(f"{RED}Error: provide a tag label. Example: session_manager.py tag v2-feature{RESET}")
|
|
136
|
+
sys.exit(1)
|
|
137
|
+
|
|
138
|
+
state = load_state()
|
|
139
|
+
current = state.get("current")
|
|
140
|
+
if not current:
|
|
141
|
+
print(f"{YELLOW}No active session — use 'save' first before tagging.{RESET}")
|
|
142
|
+
sys.exit(1)
|
|
143
|
+
|
|
144
|
+
# Tag must be applied to the same entry in history
|
|
145
|
+
current.setdefault("tags", [])
|
|
146
|
+
if label in current["tags"]:
|
|
147
|
+
print(f"{YELLOW}Tag '{label}' already exists on session #{current['session']}.{RESET}")
|
|
148
|
+
return
|
|
149
|
+
|
|
150
|
+
current["tags"].append(label)
|
|
151
|
+
state["current"] = current
|
|
152
|
+
|
|
153
|
+
# Also update the matching entry in history
|
|
154
|
+
for entry in state.get("history", []):
|
|
155
|
+
if entry.get("session") == current["session"]:
|
|
156
|
+
entry.setdefault("tags", [])
|
|
157
|
+
if label not in entry["tags"]:
|
|
158
|
+
entry["tags"].append(label)
|
|
159
|
+
break
|
|
160
|
+
|
|
161
|
+
save_state(state)
|
|
162
|
+
print(f"{GREEN}✅ Tagged session #{current['session']} with '{label}'.{RESET}")
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def cmd_list(show_all: bool = False) -> None:
|
|
166
|
+
"""Paginated list of all session history."""
|
|
167
|
+
state = load_state()
|
|
168
|
+
history = state.get("history", [])
|
|
169
|
+
if not history:
|
|
170
|
+
print(f"{YELLOW}No session history.{RESET}")
|
|
171
|
+
return
|
|
172
|
+
|
|
173
|
+
total = len(history)
|
|
174
|
+
page_size = len(history) if show_all else LIST_PAGE_SIZE
|
|
175
|
+
recent = list(reversed(history))[:page_size]
|
|
176
|
+
|
|
177
|
+
print(f"\n{BOLD}{CYAN}━━━ Session List ({total} total, showing {len(recent)}) ━━━━━━━{RESET}")
|
|
178
|
+
for entry in recent:
|
|
179
|
+
tags_str = (", ".join(entry.get("tags", []))) or ""
|
|
180
|
+
tags_display = f" [{YELLOW}{tags_str}{RESET}]" if tags_str else ""
|
|
181
|
+
ts = entry["timestamp"][:16]
|
|
182
|
+
print(f"\n {BOLD}{BLUE}#{entry['session']}{RESET} — {ts}{tags_display}")
|
|
183
|
+
print(f" {entry['note']}")
|
|
184
|
+
|
|
185
|
+
if not show_all and total > page_size:
|
|
186
|
+
remaining = total - page_size
|
|
187
|
+
print(f"\n {YELLOW}... {remaining} older session(s) not shown. Use 'list --all' to see all.{RESET}")
|
|
188
|
+
|
|
189
|
+
print(f"{CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{RESET}\n")
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def cmd_export(to_stdout: bool = False) -> None:
|
|
193
|
+
"""Export all session history to a Markdown file or stdout."""
|
|
194
|
+
state = load_state()
|
|
195
|
+
history = state.get("history", [])
|
|
196
|
+
if not history:
|
|
197
|
+
print(f"{YELLOW}No session history to export.{RESET}")
|
|
198
|
+
return
|
|
199
|
+
|
|
200
|
+
lines = ["# Session Export\n"]
|
|
201
|
+
lines.append(f"Generated: {datetime.now().isoformat()[:16]}\n")
|
|
202
|
+
lines.append(f"Total sessions: {len(history)}\n\n---\n")
|
|
203
|
+
|
|
204
|
+
for entry in reversed(history):
|
|
205
|
+
session_num = entry.get("session", "?")
|
|
206
|
+
ts = entry.get("timestamp", "")[:16]
|
|
207
|
+
note = entry.get("note", "")
|
|
208
|
+
tags = entry.get("tags", [])
|
|
209
|
+
tags_str = f"\n**Tags:** {', '.join(tags)}" if tags else ""
|
|
210
|
+
lines.append(f"## Session #{session_num} — {ts}\n")
|
|
211
|
+
lines.append(f"{note}{tags_str}\n\n---\n")
|
|
212
|
+
|
|
213
|
+
content = "\n".join(lines)
|
|
214
|
+
|
|
215
|
+
if to_stdout:
|
|
216
|
+
print(content)
|
|
217
|
+
else:
|
|
218
|
+
export_path = Path("session_export.md")
|
|
219
|
+
with open(export_path, "w", encoding="utf-8") as f:
|
|
220
|
+
f.write(content)
|
|
221
|
+
print(f"{GREEN}✅ Exported {len(history)} sessions to{RESET} {export_path}")
|
|
222
|
+
|
|
223
|
+
|
|
94
224
|
def main() -> None:
|
|
95
225
|
if len(sys.argv) < 2:
|
|
96
|
-
print(f"Usage: session_manager.py [save <note>|load|show|clear]")
|
|
226
|
+
print(f"Usage: session_manager.py [save <note>|load|show|clear|status|tag <label>|list [--all]|export [--stdout]]")
|
|
97
227
|
sys.exit(1)
|
|
98
228
|
|
|
99
229
|
cmd = sys.argv[1].lower()
|
|
@@ -114,6 +244,17 @@ def main() -> None:
|
|
|
114
244
|
cmd_show()
|
|
115
245
|
elif cmd == "clear":
|
|
116
246
|
cmd_clear()
|
|
247
|
+
elif cmd == "status":
|
|
248
|
+
cmd_status()
|
|
249
|
+
elif cmd == "tag":
|
|
250
|
+
label = " ".join(sys.argv[2:]).strip()
|
|
251
|
+
cmd_tag(label)
|
|
252
|
+
elif cmd == "list":
|
|
253
|
+
show_all = "--all" in sys.argv
|
|
254
|
+
cmd_list(show_all)
|
|
255
|
+
elif cmd == "export":
|
|
256
|
+
to_stdout = "--stdout" in sys.argv
|
|
257
|
+
cmd_export(to_stdout)
|
|
117
258
|
|
|
118
259
|
|
|
119
260
|
if __name__ == "__main__":
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
skill_integrator.py — Automated Skill-Script Integration Analyzer
|
|
4
|
+
|
|
5
|
+
This script scans active skills in `.agent/skills/` and maps them to their
|
|
6
|
+
corresponding executable scripts in `.agent/scripts/`. It helps the Orchestrator
|
|
7
|
+
and other agents know which skills have automated CLI actions available.
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
python .agent/scripts/skill_integrator.py
|
|
11
|
+
python .agent/scripts/skill_integrator.py --skill <skill-name>
|
|
12
|
+
python .agent/scripts/skill_integrator.py --report
|
|
13
|
+
python .agent/scripts/skill_integrator.py --verify
|
|
14
|
+
python .agent/scripts/skill_integrator.py --report --verify
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import ast
|
|
18
|
+
import argparse
|
|
19
|
+
import os
|
|
20
|
+
import re
|
|
21
|
+
import sys
|
|
22
|
+
from datetime import datetime
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
# Colors for terminal output
|
|
26
|
+
CYAN = "\033[96m"
|
|
27
|
+
GREEN = "\033[92m"
|
|
28
|
+
YELLOW = "\033[93m"
|
|
29
|
+
RED = "\033[91m"
|
|
30
|
+
BOLD = "\033[1m"
|
|
31
|
+
RESET = "\033[0m"
|
|
32
|
+
|
|
33
|
+
REPORT_FILE = "skill-integration-report.md"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def find_agent_dir(start_path: Path) -> Path:
|
|
37
|
+
current = start_path.resolve()
|
|
38
|
+
while current != current.parent:
|
|
39
|
+
agent_dir = current / '.agent'
|
|
40
|
+
if agent_dir.exists() and agent_dir.is_dir():
|
|
41
|
+
return agent_dir
|
|
42
|
+
current = current.parent
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def get_associated_script(skill_dir: Path, scripts_dir: Path) -> str:
|
|
47
|
+
"""Check if the skill has an explicit frontmatter script or an implicit script file."""
|
|
48
|
+
skill_name = skill_dir.name
|
|
49
|
+
|
|
50
|
+
# 1. Implicit check: does a script with the same name exist?
|
|
51
|
+
implicit_script = scripts_dir / f"{skill_name}.py"
|
|
52
|
+
if implicit_script.exists():
|
|
53
|
+
return f".agent/scripts/{skill_name}.py"
|
|
54
|
+
|
|
55
|
+
# 2. Explicit check: does the SKILL.md define 'script:' in its frontmatter?
|
|
56
|
+
skill_md = skill_dir / "SKILL.md"
|
|
57
|
+
if skill_md.exists():
|
|
58
|
+
try:
|
|
59
|
+
with open(skill_md, "r", encoding="utf-8") as f:
|
|
60
|
+
content = f.read()
|
|
61
|
+
match = re.search(r"---(.*?)---", content, re.DOTALL)
|
|
62
|
+
if match:
|
|
63
|
+
frontmatter = match.group(1)
|
|
64
|
+
script_match = re.search(r"(?:^|\n)script:\s*([^\n]+)", frontmatter)
|
|
65
|
+
if script_match:
|
|
66
|
+
return script_match.group(1).strip()
|
|
67
|
+
except Exception:
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def scan_all_skills(agent_dir: Path) -> dict:
|
|
74
|
+
skills_dir = agent_dir / "skills"
|
|
75
|
+
scripts_dir = agent_dir / "scripts"
|
|
76
|
+
|
|
77
|
+
if not skills_dir.exists() or not scripts_dir.exists():
|
|
78
|
+
print(f"{YELLOW}Warning: '.agent/skills' or '.agent/scripts' directory not found.{RESET}")
|
|
79
|
+
return {}
|
|
80
|
+
|
|
81
|
+
integrated_skills = {}
|
|
82
|
+
|
|
83
|
+
for item in sorted(skills_dir.iterdir()):
|
|
84
|
+
if item.is_dir():
|
|
85
|
+
script_path = get_associated_script(item, scripts_dir)
|
|
86
|
+
if script_path:
|
|
87
|
+
integrated_skills[item.name] = script_path
|
|
88
|
+
|
|
89
|
+
return integrated_skills
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def verify_script(script_path_str: str, workspace_root: Path) -> tuple[bool, str]:
|
|
93
|
+
"""
|
|
94
|
+
Verify a mapped script exists on disk and has valid Python syntax.
|
|
95
|
+
Returns (is_valid: bool, message: str).
|
|
96
|
+
"""
|
|
97
|
+
# Resolve relative path from workspace root
|
|
98
|
+
script_path = workspace_root / script_path_str
|
|
99
|
+
|
|
100
|
+
if not script_path.exists():
|
|
101
|
+
return False, f"File not found: {script_path}"
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
source = script_path.read_text(encoding="utf-8")
|
|
105
|
+
ast.parse(source)
|
|
106
|
+
return True, "Syntax OK"
|
|
107
|
+
except SyntaxError as e:
|
|
108
|
+
return False, f"Syntax error at line {e.lineno}: {e.msg}"
|
|
109
|
+
except Exception as e:
|
|
110
|
+
return False, f"Parse error: {e}"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def check_skill(skill_name: str, agent_dir: Path) -> None:
|
|
114
|
+
skill_dir = agent_dir / "skills" / skill_name
|
|
115
|
+
scripts_dir = agent_dir / "scripts"
|
|
116
|
+
|
|
117
|
+
if not skill_dir.exists():
|
|
118
|
+
print(f"{YELLOW}Skill '{skill_name}' not found in .agent/skills/{RESET}")
|
|
119
|
+
return
|
|
120
|
+
|
|
121
|
+
script_path = get_associated_script(skill_dir, scripts_dir)
|
|
122
|
+
if script_path:
|
|
123
|
+
print(f"{GREEN}✓ Associated script found:{RESET} {script_path}")
|
|
124
|
+
print(f"\nTo execute:\n python {script_path}")
|
|
125
|
+
else:
|
|
126
|
+
print(f"No executable script mapped for '{skill_name}'.")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def cmd_report(integrated_skills: dict, workspace_root: Path) -> None:
|
|
130
|
+
"""Write a Markdown integration report to REPORT_FILE."""
|
|
131
|
+
lines = [
|
|
132
|
+
"# Skill-Script Integration Report\n",
|
|
133
|
+
f"Generated: {datetime.now().isoformat()[:16]}\n",
|
|
134
|
+
f"Integrated skills: {len(integrated_skills)}\n",
|
|
135
|
+
"\n---\n",
|
|
136
|
+
"\n| Skill | Script | Exists |\n",
|
|
137
|
+
"|---|---|---|\n",
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
for skill, script in sorted(integrated_skills.items()):
|
|
141
|
+
script_path = workspace_root / script
|
|
142
|
+
exists = "✅" if script_path.exists() else "❌ Missing"
|
|
143
|
+
lines.append(f"| `{skill}` | `{script}` | {exists} |\n")
|
|
144
|
+
|
|
145
|
+
lines.append("\n---\n")
|
|
146
|
+
lines.append(f"\n_Run `python .agent/scripts/skill_integrator.py --verify` to validate syntax of all mapped scripts._\n")
|
|
147
|
+
|
|
148
|
+
content = "".join(lines)
|
|
149
|
+
report_path = workspace_root / REPORT_FILE
|
|
150
|
+
with open(report_path, "w", encoding="utf-8") as f:
|
|
151
|
+
f.write(content)
|
|
152
|
+
|
|
153
|
+
print(f"{GREEN}✅ Report written to:{RESET} {report_path}")
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def cmd_verify(integrated_skills: dict, workspace_root: Path) -> bool:
|
|
157
|
+
"""
|
|
158
|
+
Validate each mapped script: check existence and Python syntax.
|
|
159
|
+
Returns True if all pass, False if any fail.
|
|
160
|
+
"""
|
|
161
|
+
if not integrated_skills:
|
|
162
|
+
print(f"{YELLOW}No integrated scripts found to verify.{RESET}")
|
|
163
|
+
return True
|
|
164
|
+
|
|
165
|
+
print(f"\n{BOLD}{CYAN}━━━ Skill-Script Verification ({len(integrated_skills)} scripts) ━━━{RESET}\n")
|
|
166
|
+
|
|
167
|
+
all_passed = True
|
|
168
|
+
for skill, script in sorted(integrated_skills.items()):
|
|
169
|
+
ok, msg = verify_script(script, workspace_root)
|
|
170
|
+
if ok:
|
|
171
|
+
print(f" {GREEN}✅ PASS{RESET} {BOLD}{skill}{RESET} → {script}")
|
|
172
|
+
else:
|
|
173
|
+
print(f" {RED}❌ FAIL{RESET} {BOLD}{skill}{RESET} → {script}")
|
|
174
|
+
print(f" {RED}{msg}{RESET}")
|
|
175
|
+
all_passed = False
|
|
176
|
+
|
|
177
|
+
print(f"\n{CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{RESET}")
|
|
178
|
+
if all_passed:
|
|
179
|
+
print(f"{GREEN}All {len(integrated_skills)} mapped scripts passed verification.{RESET}\n")
|
|
180
|
+
else:
|
|
181
|
+
failed = sum(1 for s, p in integrated_skills.items() if not verify_script(p, workspace_root)[0])
|
|
182
|
+
print(f"{RED}{failed} script(s) failed verification. Fix before deploying.{RESET}\n")
|
|
183
|
+
|
|
184
|
+
return all_passed
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def main():
|
|
188
|
+
parser = argparse.ArgumentParser(description="Skill-Script Integrator")
|
|
189
|
+
parser.add_argument("--skill", type=str, help="Validate a specific skill by name")
|
|
190
|
+
parser.add_argument("--workspace", type=str, default=".", help="Workspace root directory")
|
|
191
|
+
parser.add_argument("--report", action="store_true",
|
|
192
|
+
help=f"Generate a Markdown integration report ({REPORT_FILE})")
|
|
193
|
+
parser.add_argument("--verify", action="store_true",
|
|
194
|
+
help="Validate syntax of all mapped scripts (exits 1 on any failure)")
|
|
195
|
+
|
|
196
|
+
args = parser.parse_args()
|
|
197
|
+
workspace_root = Path(args.workspace).resolve()
|
|
198
|
+
|
|
199
|
+
agent_dir = find_agent_dir(workspace_root)
|
|
200
|
+
if not agent_dir:
|
|
201
|
+
print(f"{YELLOW}Error: Could not find .agent directory starting from {workspace_root}{RESET}")
|
|
202
|
+
sys.exit(1)
|
|
203
|
+
|
|
204
|
+
if args.skill:
|
|
205
|
+
check_skill(args.skill, agent_dir)
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
integrated_skills = scan_all_skills(agent_dir)
|
|
209
|
+
|
|
210
|
+
# --report: always runs first (independent of --verify)
|
|
211
|
+
if args.report:
|
|
212
|
+
cmd_report(integrated_skills, workspace_root)
|
|
213
|
+
|
|
214
|
+
# --verify: validate all mapped scripts
|
|
215
|
+
if args.verify:
|
|
216
|
+
passed = cmd_verify(integrated_skills, workspace_root)
|
|
217
|
+
if not passed:
|
|
218
|
+
sys.exit(1)
|
|
219
|
+
return
|
|
220
|
+
|
|
221
|
+
# Default: print to terminal (original behaviour)
|
|
222
|
+
if not args.report and not args.verify:
|
|
223
|
+
if not integrated_skills:
|
|
224
|
+
print("No integrated scripts found for any active skills.")
|
|
225
|
+
else:
|
|
226
|
+
print(f"\n{BOLD}{CYAN}--- Skill-Script Integrations ({len(integrated_skills)}) ---{RESET}\n")
|
|
227
|
+
for skill, script in sorted(integrated_skills.items()):
|
|
228
|
+
print(f" {BOLD}{skill}{RESET}")
|
|
229
|
+
print(f" ↳ {GREEN}{script}{RESET}\n")
|
|
230
|
+
print(f"{CYAN}To run a skill script, use: python <path>{RESET}\n")
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
if __name__ == "__main__":
|
|
234
|
+
main()
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
strengthen_skills.py — Appends Tribunal guardrails to SKILL.md files missing them.
|
|
4
|
+
|
|
5
|
+
Adds the following sections if not already present:
|
|
6
|
+
- ## 🤖 LLM-Specific Traps
|
|
7
|
+
- ## 🏛️ Tribunal Integration (Anti-Hallucination)
|
|
8
|
+
- ❌ Forbidden AI Tropes
|
|
9
|
+
- ✅ Pre-Flight Self-Audit
|
|
10
|
+
- 🛑 Verification-Before-Completion (VBC) Protocol
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
python .agent/scripts/strengthen_skills.py .
|
|
14
|
+
python .agent/scripts/strengthen_skills.py . --dry-run
|
|
15
|
+
python .agent/scripts/strengthen_skills.py . --skill python-pro
|
|
16
|
+
python .agent/scripts/strengthen_skills.py . --path /custom/skills/dir
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import os
|
|
20
|
+
import sys
|
|
21
|
+
import argparse
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
RED = "\033[91m"
|
|
25
|
+
GREEN = "\033[92m"
|
|
26
|
+
YELLOW = "\033[93m"
|
|
27
|
+
BLUE = "\033[94m"
|
|
28
|
+
BOLD = "\033[1m"
|
|
29
|
+
RESET = "\033[0m"
|
|
30
|
+
|
|
31
|
+
# ─── Rich guardrails block ────────────────────────────────────────────────────
|
|
32
|
+
# This is the canonical template appended to skills missing Tribunal sections.
|
|
33
|
+
|
|
34
|
+
GUARDRAILS_BLOCK = """
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 🤖 LLM-Specific Traps
|
|
39
|
+
|
|
40
|
+
AI coding assistants often fall into specific bad habits when dealing with this domain. These are strictly forbidden:
|
|
41
|
+
|
|
42
|
+
1. **Over-engineering:** Proposing complex abstractions or distributed systems when a simpler approach suffices.
|
|
43
|
+
2. **Hallucinated Libraries/Methods:** Using non-existent methods or packages. Always `// VERIFY` or check `package.json` / `requirements.txt`.
|
|
44
|
+
3. **Skipping Edge Cases:** Writing the "happy path" and ignoring error handling, timeouts, or data validation.
|
|
45
|
+
4. **Context Amnesia:** Forgetting the user's constraints and offering generic advice instead of tailored solutions.
|
|
46
|
+
5. **Silent Degradation:** Catching and suppressing errors without logging or re-raising.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 🏛️ Tribunal Integration (Anti-Hallucination)
|
|
51
|
+
|
|
52
|
+
**Slash command: `/review` or `/tribunal-full`**
|
|
53
|
+
**Active reviewers: `logic-reviewer` · `security-auditor`**
|
|
54
|
+
|
|
55
|
+
### ❌ Forbidden AI Tropes
|
|
56
|
+
|
|
57
|
+
1. **Blind Assumptions:** Never make an assumption without documenting it clearly with `// VERIFY: [reason]`.
|
|
58
|
+
2. **Silent Degradation:** Catching and suppressing errors without logging or handling.
|
|
59
|
+
3. **Context Amnesia:** Forgetting the user's constraints and offering generic advice instead of tailored solutions.
|
|
60
|
+
|
|
61
|
+
### ✅ Pre-Flight Self-Audit
|
|
62
|
+
|
|
63
|
+
Review these questions before confirming output:
|
|
64
|
+
```
|
|
65
|
+
✅ Did I rely ONLY on real, verified tools and methods?
|
|
66
|
+
✅ Is this solution appropriately scoped to the user's constraints?
|
|
67
|
+
✅ Did I handle potential failure modes and edge cases?
|
|
68
|
+
✅ Have I avoided generic boilerplate that doesn't add value?
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 🛑 Verification-Before-Completion (VBC) Protocol
|
|
72
|
+
|
|
73
|
+
**CRITICAL:** You must follow a strict "evidence-based closeout" state machine.
|
|
74
|
+
- ❌ **Forbidden:** Declaring a task complete because the output "looks correct."
|
|
75
|
+
- ✅ **Required:** You are explicitly forbidden from finalizing any task without providing **concrete evidence** (terminal output, passing tests, compile success, or equivalent proof) that your output works as intended.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
# ─── Detection ───────────────────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
TRIBUNAL_MARKERS = [
|
|
81
|
+
"Tribunal Integration",
|
|
82
|
+
"Tribunal Integration (Anti-Hallucination)",
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
VBC_MARKERS = [
|
|
86
|
+
"Verification-Before-Completion",
|
|
87
|
+
"VBC Protocol",
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def has_tribunal_block(content: str) -> bool:
|
|
92
|
+
return any(marker in content for marker in TRIBUNAL_MARKERS)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def has_vbc_block(content: str) -> bool:
|
|
96
|
+
return any(marker in content for marker in VBC_MARKERS)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# ─── Output helpers ───────────────────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
def header(title: str) -> None:
|
|
102
|
+
print(f"\n{BOLD}{BLUE}━━━ {title} ━━━{RESET}")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def ok(msg: str) -> None:
|
|
106
|
+
print(f" {GREEN}✅ {msg}{RESET}")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def skip(msg: str) -> None:
|
|
110
|
+
print(f" {YELLOW}⏭️ {msg}{RESET}")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def warn(msg: str) -> None:
|
|
114
|
+
print(f" {YELLOW}⚠️ {msg}{RESET}")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def fail(msg: str) -> None:
|
|
118
|
+
print(f" {RED}❌ {msg}{RESET}")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# ─── Core ────────────────────────────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
def process_skill(skill_md: Path, dry_run: bool) -> str:
|
|
124
|
+
"""Process a single SKILL.md. Returns 'updated', 'skipped', or 'error'."""
|
|
125
|
+
skill_name = skill_md.parent.name
|
|
126
|
+
try:
|
|
127
|
+
content = skill_md.read_text(encoding="utf-8")
|
|
128
|
+
|
|
129
|
+
has_tribunal = has_tribunal_block(content)
|
|
130
|
+
has_vbc = has_vbc_block(content)
|
|
131
|
+
|
|
132
|
+
if has_tribunal and has_vbc:
|
|
133
|
+
skip(f"{skill_name} — already has Tribunal + VBC blocks")
|
|
134
|
+
return "skipped"
|
|
135
|
+
|
|
136
|
+
missing = []
|
|
137
|
+
if not has_tribunal:
|
|
138
|
+
missing.append("Tribunal Integration")
|
|
139
|
+
if not has_vbc:
|
|
140
|
+
missing.append("VBC Protocol")
|
|
141
|
+
|
|
142
|
+
if dry_run:
|
|
143
|
+
warn(f"[DRY RUN] {skill_name} — would add: {', '.join(missing)}")
|
|
144
|
+
return "updated"
|
|
145
|
+
|
|
146
|
+
with skill_md.open("a", encoding="utf-8") as f:
|
|
147
|
+
f.write(GUARDRAILS_BLOCK)
|
|
148
|
+
|
|
149
|
+
ok(f"{skill_name} — strengthened ({', '.join(missing)} added)")
|
|
150
|
+
return "updated"
|
|
151
|
+
|
|
152
|
+
except Exception as e:
|
|
153
|
+
fail(f"{skill_name} — {e}")
|
|
154
|
+
return "error"
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# ─── Main ────────────────────────────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
def main() -> None:
|
|
160
|
+
parser = argparse.ArgumentParser(
|
|
161
|
+
description="Appends Tribunal guardrails (LLM Traps + Pre-Flight + VBC) to skills missing them"
|
|
162
|
+
)
|
|
163
|
+
parser.add_argument(
|
|
164
|
+
"path",
|
|
165
|
+
nargs="?",
|
|
166
|
+
default=".",
|
|
167
|
+
help="Project root directory (default: current directory)"
|
|
168
|
+
)
|
|
169
|
+
parser.add_argument("--dry-run", action="store_true", help="Show changes without writing")
|
|
170
|
+
parser.add_argument("--skill", help="Only strengthen a specific skill by name")
|
|
171
|
+
parser.add_argument(
|
|
172
|
+
"--skills-path",
|
|
173
|
+
help="Override the skills directory path (default: <path>/.agent/skills)"
|
|
174
|
+
)
|
|
175
|
+
args = parser.parse_args()
|
|
176
|
+
|
|
177
|
+
project_root = Path(args.path).resolve()
|
|
178
|
+
skills_dir = (
|
|
179
|
+
Path(args.skills_path).resolve()
|
|
180
|
+
if args.skills_path
|
|
181
|
+
else project_root / ".agent" / "skills"
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
if not skills_dir.is_dir():
|
|
185
|
+
fail(f"Skills directory not found: {skills_dir}")
|
|
186
|
+
sys.exit(1)
|
|
187
|
+
|
|
188
|
+
print(f"{BOLD}Tribunal — strengthen_skills.py{RESET}")
|
|
189
|
+
if args.dry_run:
|
|
190
|
+
print(f" {YELLOW}DRY RUN — no files will be written{RESET}")
|
|
191
|
+
print(f"Skills dir: {skills_dir}\n")
|
|
192
|
+
|
|
193
|
+
counts: dict[str, int] = {"updated": 0, "skipped": 0, "error": 0}
|
|
194
|
+
|
|
195
|
+
header("Strengthening Skills")
|
|
196
|
+
for skill_dir in sorted(skills_dir.iterdir()):
|
|
197
|
+
if not skill_dir.is_dir():
|
|
198
|
+
continue
|
|
199
|
+
if args.skill and skill_dir.name != args.skill:
|
|
200
|
+
continue
|
|
201
|
+
skill_md = skill_dir / "SKILL.md"
|
|
202
|
+
if not skill_md.exists():
|
|
203
|
+
warn(f"{skill_dir.name} — no SKILL.md found")
|
|
204
|
+
continue
|
|
205
|
+
result = process_skill(skill_md, args.dry_run)
|
|
206
|
+
counts[result] += 1
|
|
207
|
+
|
|
208
|
+
print(f"\n{BOLD}━━━ Summary ━━━{RESET}")
|
|
209
|
+
print(f" {GREEN}✅ Strengthened: {counts['updated']}{RESET}")
|
|
210
|
+
print(f" {YELLOW}⏭️ Skipped: {counts['skipped']}{RESET}")
|
|
211
|
+
if counts["error"]:
|
|
212
|
+
print(f" {RED}❌ Errors: {counts['error']}{RESET}")
|
|
213
|
+
if args.dry_run:
|
|
214
|
+
print(f" {YELLOW}(dry-run — nothing written){RESET}")
|
|
215
|
+
|
|
216
|
+
sys.exit(1 if counts["error"] > 0 else 0)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
if __name__ == "__main__":
|
|
220
|
+
main()
|