tribunal-kit 4.2.0 → 4.3.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.
Files changed (186) hide show
  1. package/.agent/ARCHITECTURE.md +21 -14
  2. package/.agent/agents/swarm-worker-contracts.md +5 -5
  3. package/.agent/agents/ui-ux-auditor.md +292 -0
  4. package/.agent/rules/GEMINI.md +8 -8
  5. package/.agent/scripts/__pycache__/_colors.cpython-311.pyc +0 -0
  6. package/.agent/scripts/__pycache__/_utils.cpython-311.pyc +0 -0
  7. package/.agent/scripts/__pycache__/case_law_manager.cpython-311.pyc +0 -0
  8. package/.agent/scripts/_colors.js +18 -0
  9. package/.agent/scripts/_utils.js +42 -0
  10. package/.agent/scripts/auto_preview.js +197 -0
  11. package/.agent/scripts/bundle_analyzer.js +290 -0
  12. package/.agent/scripts/case_law_manager.js +684 -0
  13. package/.agent/scripts/checklist.js +266 -0
  14. package/.agent/scripts/colors.js +17 -0
  15. package/.agent/scripts/compress_skills.js +141 -0
  16. package/.agent/scripts/consolidate_skills.js +149 -0
  17. package/.agent/scripts/context_broker.js +609 -0
  18. package/.agent/scripts/deep_compress.js +150 -0
  19. package/.agent/scripts/dependency_analyzer.js +272 -0
  20. package/.agent/scripts/graph_builder.js +199 -0
  21. package/.agent/scripts/graph_zoom.js +154 -0
  22. package/.agent/scripts/inner_loop_validator.js +465 -0
  23. package/.agent/scripts/lint_runner.js +187 -0
  24. package/.agent/scripts/minify_context.js +100 -0
  25. package/.agent/scripts/patch_skills_meta.js +156 -0
  26. package/.agent/scripts/patch_skills_output.js +244 -0
  27. package/.agent/scripts/schema_validator.js +297 -0
  28. package/.agent/scripts/security_scan.js +303 -0
  29. package/.agent/scripts/session_manager.js +276 -0
  30. package/.agent/scripts/skill_evolution.js +644 -0
  31. package/.agent/scripts/skill_integrator.js +313 -0
  32. package/.agent/scripts/strengthen_skills.js +193 -0
  33. package/.agent/scripts/strip_tribunal.js +47 -0
  34. package/.agent/scripts/swarm_dispatcher.js +360 -0
  35. package/.agent/scripts/test_runner.js +193 -0
  36. package/.agent/scripts/utils.js +32 -0
  37. package/.agent/scripts/verify_all.js +256 -0
  38. package/.agent/skills/agent-organizer/SKILL.md +12 -4
  39. package/.agent/skills/agentic-patterns/SKILL.md +12 -4
  40. package/.agent/skills/ai-prompt-injection-defense/SKILL.md +12 -4
  41. package/.agent/skills/api-patterns/SKILL.md +209 -201
  42. package/.agent/skills/api-security-auditor/SKILL.md +12 -4
  43. package/.agent/skills/app-builder/SKILL.md +12 -4
  44. package/.agent/skills/app-builder/templates/SKILL.md +76 -68
  45. package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +1 -1
  46. package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +1 -1
  47. package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +1 -1
  48. package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +1 -1
  49. package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +1 -1
  50. package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +1 -1
  51. package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +1 -1
  52. package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +1 -1
  53. package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +1 -1
  54. package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +1 -1
  55. package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +1 -1
  56. package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +1 -1
  57. package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +1 -1
  58. package/.agent/skills/appflow-wireframe/SKILL.md +12 -4
  59. package/.agent/skills/architecture/SKILL.md +12 -4
  60. package/.agent/skills/authentication-best-practices/SKILL.md +12 -4
  61. package/.agent/skills/bash-linux/SKILL.md +12 -4
  62. package/.agent/skills/behavioral-modes/SKILL.md +12 -4
  63. package/.agent/skills/brainstorming/SKILL.md +12 -4
  64. package/.agent/skills/building-native-ui/SKILL.md +12 -4
  65. package/.agent/skills/clean-code/SKILL.md +12 -4
  66. package/.agent/skills/code-review-checklist/SKILL.md +12 -4
  67. package/.agent/skills/config-validator/SKILL.md +12 -4
  68. package/.agent/skills/csharp-developer/SKILL.md +12 -4
  69. package/.agent/skills/data-validation-schemas/SKILL.md +290 -282
  70. package/.agent/skills/database-design/SKILL.md +202 -194
  71. package/.agent/skills/deployment-procedures/SKILL.md +12 -4
  72. package/.agent/skills/devops-engineer/SKILL.md +12 -4
  73. package/.agent/skills/devops-incident-responder/SKILL.md +12 -4
  74. package/.agent/skills/doc.md +1 -1
  75. package/.agent/skills/documentation-templates/SKILL.md +12 -4
  76. package/.agent/skills/edge-computing/SKILL.md +12 -4
  77. package/.agent/skills/error-resilience/SKILL.md +390 -382
  78. package/.agent/skills/extract-design-system/SKILL.md +12 -4
  79. package/.agent/skills/framer-motion-expert/SKILL.md +206 -199
  80. package/.agent/skills/frontend-design/SKILL.md +163 -155
  81. package/.agent/skills/game-design-expert/SKILL.md +12 -4
  82. package/.agent/skills/game-engineering-expert/SKILL.md +12 -4
  83. package/.agent/skills/geo-fundamentals/SKILL.md +12 -4
  84. package/.agent/skills/github-operations/SKILL.md +12 -4
  85. package/.agent/skills/gsap-core/SKILL.md +54 -48
  86. package/.agent/skills/gsap-frameworks/SKILL.md +54 -48
  87. package/.agent/skills/gsap-performance/SKILL.md +54 -48
  88. package/.agent/skills/gsap-plugins/SKILL.md +54 -48
  89. package/.agent/skills/gsap-react/SKILL.md +54 -48
  90. package/.agent/skills/gsap-scrolltrigger/SKILL.md +54 -48
  91. package/.agent/skills/gsap-timeline/SKILL.md +54 -48
  92. package/.agent/skills/gsap-utils/SKILL.md +54 -48
  93. package/.agent/skills/i18n-localization/SKILL.md +12 -4
  94. package/.agent/skills/intelligent-routing/SKILL.md +41 -33
  95. package/.agent/skills/knowledge-graph/SKILL.md +36 -0
  96. package/.agent/skills/lint-and-validate/SKILL.md +12 -4
  97. package/.agent/skills/llm-engineering/SKILL.md +12 -4
  98. package/.agent/skills/local-first/SKILL.md +12 -4
  99. package/.agent/skills/mcp-builder/SKILL.md +12 -4
  100. package/.agent/skills/mobile-design/SKILL.md +225 -217
  101. package/.agent/skills/monorepo-management/SKILL.md +296 -288
  102. package/.agent/skills/motion-engineering/SKILL.md +195 -187
  103. package/.agent/skills/nextjs-react-expert/SKILL.md +196 -188
  104. package/.agent/skills/nodejs-best-practices/SKILL.md +12 -4
  105. package/.agent/skills/observability/SKILL.md +12 -4
  106. package/.agent/skills/parallel-agents/SKILL.md +12 -4
  107. package/.agent/skills/performance-profiling/SKILL.md +12 -4
  108. package/.agent/skills/plan-writing/SKILL.md +12 -4
  109. package/.agent/skills/platform-engineer/SKILL.md +12 -4
  110. package/.agent/skills/playwright-best-practices/SKILL.md +12 -4
  111. package/.agent/skills/powershell-windows/SKILL.md +12 -4
  112. package/.agent/skills/project-idioms/SKILL.md +12 -4
  113. package/.agent/skills/python-patterns/SKILL.md +12 -4
  114. package/.agent/skills/python-pro/SKILL.md +285 -277
  115. package/.agent/skills/react-specialist/SKILL.md +239 -231
  116. package/.agent/skills/readme-builder/SKILL.md +12 -4
  117. package/.agent/skills/realtime-patterns/SKILL.md +12 -4
  118. package/.agent/skills/red-team-tactics/SKILL.md +12 -4
  119. package/.agent/skills/rust-pro/SKILL.md +12 -4
  120. package/.agent/skills/seo-fundamentals/SKILL.md +12 -4
  121. package/.agent/skills/server-management/SKILL.md +12 -4
  122. package/.agent/skills/shadcn-ui-expert/SKILL.md +12 -4
  123. package/.agent/skills/skill-creator/SKILL.md +12 -4
  124. package/.agent/skills/sql-pro/SKILL.md +12 -4
  125. package/.agent/skills/supabase-postgres-best-practices/SKILL.md +12 -4
  126. package/.agent/skills/swiftui-expert/SKILL.md +12 -4
  127. package/.agent/skills/systematic-debugging/SKILL.md +12 -4
  128. package/.agent/skills/tailwind-patterns/SKILL.md +12 -4
  129. package/.agent/skills/tdd-workflow/SKILL.md +12 -4
  130. package/.agent/skills/test-result-analyzer/SKILL.md +12 -4
  131. package/.agent/skills/testing-patterns/SKILL.md +12 -4
  132. package/.agent/skills/trend-researcher/SKILL.md +12 -4
  133. package/.agent/skills/typescript-advanced/SKILL.md +297 -289
  134. package/.agent/skills/ui-ux-pro-max/SKILL.md +12 -4
  135. package/.agent/skills/ui-ux-researcher/SKILL.md +12 -4
  136. package/.agent/skills/vue-expert/SKILL.md +237 -229
  137. package/.agent/skills/vulnerability-scanner/SKILL.md +12 -4
  138. package/.agent/skills/web-accessibility-auditor/SKILL.md +12 -4
  139. package/.agent/skills/web-design-guidelines/SKILL.md +12 -4
  140. package/.agent/skills/webapp-testing/SKILL.md +12 -4
  141. package/.agent/skills/whimsy-injector/SKILL.md +12 -4
  142. package/.agent/skills/workflow-optimizer/SKILL.md +12 -4
  143. package/.agent/workflows/audit.md +6 -6
  144. package/.agent/workflows/deploy.md +1 -1
  145. package/.agent/workflows/generate.md +23 -6
  146. package/.agent/workflows/session.md +5 -5
  147. package/.agent/workflows/swarm.md +2 -2
  148. package/README.md +242 -186
  149. package/bin/tribunal-kit.js +297 -57
  150. package/package.json +81 -77
  151. package/scripts/changelog.js +167 -0
  152. package/scripts/sync-version.js +81 -0
  153. package/scripts/validate-payload.js +73 -0
  154. package/.agent/scripts/__pycache__/auto_preview.cpython-311.pyc +0 -0
  155. package/.agent/scripts/__pycache__/bundle_analyzer.cpython-311.pyc +0 -0
  156. package/.agent/scripts/__pycache__/checklist.cpython-311.pyc +0 -0
  157. package/.agent/scripts/__pycache__/dependency_analyzer.cpython-311.pyc +0 -0
  158. package/.agent/scripts/__pycache__/security_scan.cpython-311.pyc +0 -0
  159. package/.agent/scripts/__pycache__/session_manager.cpython-311.pyc +0 -0
  160. package/.agent/scripts/__pycache__/skill_integrator.cpython-311.pyc +0 -0
  161. package/.agent/scripts/__pycache__/swarm_dispatcher.cpython-311.pyc +0 -0
  162. package/.agent/scripts/__pycache__/test_runner.cpython-311.pyc +0 -0
  163. package/.agent/scripts/__pycache__/verify_all.cpython-311.pyc +0 -0
  164. package/.agent/scripts/auto_preview.py +0 -180
  165. package/.agent/scripts/bundle_analyzer.py +0 -259
  166. package/.agent/scripts/case_law_manager.py +0 -755
  167. package/.agent/scripts/checklist.py +0 -209
  168. package/.agent/scripts/compress_skills.py +0 -167
  169. package/.agent/scripts/consolidate_skills.py +0 -173
  170. package/.agent/scripts/deep_compress.py +0 -202
  171. package/.agent/scripts/dependency_analyzer.py +0 -247
  172. package/.agent/scripts/lint_runner.py +0 -188
  173. package/.agent/scripts/minify_context.py +0 -80
  174. package/.agent/scripts/patch_skills_meta.py +0 -177
  175. package/.agent/scripts/patch_skills_output.py +0 -285
  176. package/.agent/scripts/schema_validator.py +0 -279
  177. package/.agent/scripts/security_scan.py +0 -224
  178. package/.agent/scripts/session_manager.py +0 -261
  179. package/.agent/scripts/skill_evolution.py +0 -563
  180. package/.agent/scripts/skill_integrator.py +0 -234
  181. package/.agent/scripts/strengthen_skills.py +0 -220
  182. package/.agent/scripts/strip_tribunal.py +0 -41
  183. package/.agent/scripts/swarm_dispatcher.py +0 -350
  184. package/.agent/scripts/test_runner.py +0 -192
  185. package/.agent/scripts/test_swarm_dispatcher.py +0 -163
  186. package/.agent/scripts/verify_all.py +0 -195
@@ -1,350 +0,0 @@
1
- import argparse
2
- import json
3
- import logging
4
- import os
5
- import sys
6
- import uuid
7
- from pathlib import Path
8
-
9
- # Configure logging
10
- logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
11
-
12
- VALID_WORKER_TYPES = {
13
- "research", "generate_code", "review_code", "debug",
14
- "plan", "design_schema", "write_docs", "security_audit",
15
- "optimize", "test"
16
- }
17
-
18
- VALID_RESULT_STATUSES = {"success", "failure", "escalate"}
19
-
20
- MAX_GOAL_LENGTH = 200
21
- MAX_CONTEXT_LENGTH = 800
22
- MAX_WORKERS_PER_SWARM = 5
23
-
24
-
25
- def find_agent_dir(start_path: Path) -> Path:
26
- current = start_path.resolve()
27
- while current != current.parent:
28
- agent_dir = current / '.agent'
29
- if agent_dir.exists() and agent_dir.is_dir():
30
- return agent_dir
31
- current = current.parent
32
- return None
33
-
34
-
35
- # ─── Legacy mode: validate orchestrator micro-worker payloads ──────────────────
36
-
37
- def validate_payload(payload_data: dict, workspace_root: Path, agents_dir: Path) -> bool:
38
- if "dispatch_micro_workers" not in payload_data:
39
- logging.error("Payload missing required 'dispatch_micro_workers' array.")
40
- return False
41
-
42
- workers = payload_data.get("dispatch_micro_workers", [])
43
- if not isinstance(workers, list):
44
- logging.error("'dispatch_micro_workers' must be a list.")
45
- return False
46
-
47
- all_valid = True
48
- for i, worker in enumerate(workers):
49
- agent_name = worker.get("target_agent")
50
- if not agent_name:
51
- logging.error(f"Worker {i}: missing 'target_agent'.")
52
- all_valid = False
53
- continue
54
-
55
- agent_file = agents_dir / f"{agent_name}.md"
56
- if not agent_file.exists():
57
- logging.error(f"Worker {i}: target_agent '{agent_name}' not found at {agent_file}.")
58
- all_valid = False
59
-
60
- files_attached = worker.get("files_attached", [])
61
- if not isinstance(files_attached, list):
62
- logging.error(f"Worker {i}: 'files_attached' must be a list.")
63
- all_valid = False
64
- continue
65
-
66
- for f in files_attached:
67
- file_path = workspace_root / f
68
- if not file_path.exists():
69
- logging.warning(f"Worker {i}: attached file '{f}' does not exist (might be a new file to create).")
70
-
71
- return all_valid
72
-
73
-
74
- def build_worker_prompts(payload_data: dict, workspace_root: Path) -> list:
75
- import subprocess
76
- prompts = []
77
-
78
- ast_context = ""
79
- try:
80
- res = subprocess.run(["python", "-m", "code_review_graph", "review-delta"], cwd=workspace_root, capture_output=True, text=True)
81
- if res.returncode == 0 and res.stdout.strip():
82
- ast_context = "\n\n[AST Blast Radius Context]:\n" + res.stdout.strip()
83
- except Exception as e:
84
- logging.warning(f"code-review-graph failed: {e}")
85
-
86
- workers = payload_data.get("dispatch_micro_workers", [])
87
- for worker in workers:
88
- agent = worker.get("target_agent")
89
- ctx = worker.get("context_summary", "")
90
- task = worker.get("task_description", "")
91
- files = worker.get("files_attached", [])
92
-
93
- prompt = f"--- MICRO-WORKER DISPATCH ---\n"
94
- prompt += f"Agent: {agent}\n"
95
- prompt += f"Context: {ctx}{ast_context}\n"
96
- prompt += f"Task: {task}\n"
97
- prompt += f"Attached Files: {', '.join(files) if files else 'None'}\n"
98
- prompt += "-----------------------------"
99
- prompts.append(prompt)
100
- return prompts
101
-
102
-
103
- # ─── Swarm mode: validate WorkerRequest / WorkerResult payloads ───────────────
104
-
105
- def validate_worker_request(req: dict, index: int, agents_dir: Path) -> list[str]:
106
- """Validate a single WorkerRequest object. Returns a list of error strings."""
107
- errors = []
108
-
109
- # task_id
110
- task_id = req.get("task_id", "")
111
- if not task_id or not isinstance(task_id, str):
112
- errors.append(f"WorkerRequest[{index}]: 'task_id' must be a non-empty string.")
113
-
114
- # type
115
- req_type = req.get("type", "")
116
- if req_type not in VALID_WORKER_TYPES:
117
- errors.append(
118
- f"WorkerRequest[{index}]: 'type' must be one of {sorted(VALID_WORKER_TYPES)}, got '{req_type}'."
119
- )
120
-
121
- # agent
122
- agent = req.get("agent", "")
123
- if not agent or not isinstance(agent, str):
124
- errors.append(f"WorkerRequest[{index}]: 'agent' must be a non-empty string.")
125
- else:
126
- agent_file = agents_dir / f"{agent}.md"
127
- if not agent_file.exists():
128
- errors.append(
129
- f"WorkerRequest[{index}]: agent '{agent}' not found at {agent_file}. "
130
- f"Only agents that exist in .agent/agents/ are valid."
131
- )
132
-
133
- # goal
134
- goal = req.get("goal", "")
135
- if not goal or not isinstance(goal, str):
136
- errors.append(f"WorkerRequest[{index}]: 'goal' must be a non-empty string.")
137
- elif len(goal) > MAX_GOAL_LENGTH:
138
- errors.append(
139
- f"WorkerRequest[{index}]: 'goal' exceeds {MAX_GOAL_LENGTH} characters ({len(goal)} chars). "
140
- f"Keep it to a single, focused sentence."
141
- )
142
-
143
- # context
144
- context = req.get("context", "")
145
- if not context or not isinstance(context, str):
146
- errors.append(f"WorkerRequest[{index}]: 'context' must be a non-empty string.")
147
- elif len(context) > MAX_CONTEXT_LENGTH:
148
- errors.append(
149
- f"WorkerRequest[{index}]: 'context' exceeds {MAX_CONTEXT_LENGTH} characters ({len(context)} chars). "
150
- f"Trim to minimal required context only."
151
- )
152
-
153
- # max_retries
154
- max_retries = req.get("max_retries")
155
- if not isinstance(max_retries, int) or not (1 <= max_retries <= 3):
156
- errors.append(
157
- f"WorkerRequest[{index}]: 'max_retries' must be an integer between 1 and 3, got '{max_retries}'."
158
- )
159
-
160
- return errors
161
-
162
-
163
- def validate_worker_result(res: dict, index: int) -> list[str]:
164
- """Validate a single WorkerResult object. Returns a list of error strings."""
165
- errors = []
166
-
167
- # task_id
168
- task_id = res.get("task_id", "")
169
- if not task_id or not isinstance(task_id, str):
170
- errors.append(f"WorkerResult[{index}]: 'task_id' must be a non-empty string.")
171
-
172
- # agent
173
- agent = res.get("agent", "")
174
- if not agent or not isinstance(agent, str):
175
- errors.append(f"WorkerResult[{index}]: 'agent' must be a non-empty string.")
176
-
177
- # status
178
- status = res.get("status", "")
179
- if status not in VALID_RESULT_STATUSES:
180
- errors.append(
181
- f"WorkerResult[{index}]: 'status' must be one of {sorted(VALID_RESULT_STATUSES)}, got '{status}'."
182
- )
183
-
184
- # output / error rules
185
- output = res.get("output", "")
186
- error = res.get("error", "")
187
- if status == "success" and not output:
188
- errors.append(f"WorkerResult[{index}]: 'output' is required when status is 'success'.")
189
- if status in ("failure", "escalate") and not error:
190
- errors.append(
191
- f"WorkerResult[{index}]: 'error' is required when status is '{status}'. "
192
- f"Be specific — 'Something went wrong' is not acceptable."
193
- )
194
-
195
- # attempts
196
- attempts = res.get("attempts")
197
- if not isinstance(attempts, int) or attempts < 1:
198
- errors.append(f"WorkerResult[{index}]: 'attempts' must be an integer >= 1, got '{attempts}'.")
199
-
200
- return errors
201
-
202
-
203
- def validate_swarm_payload(payload_data, agents_dir: Path) -> bool:
204
- """
205
- Validate a Swarm payload. Accepts either:
206
- - A single WorkerRequest object (dict with 'task_id', 'type', 'agent', ...)
207
- - A single WorkerResult object (dict with 'task_id', 'agent', 'status', ...)
208
- - A list of WorkerRequests
209
- - A list of WorkerResults
210
- - An object with a top-level 'workers' array of WorkerRequests
211
- """
212
- # Normalise to a list
213
- if isinstance(payload_data, dict):
214
- if "workers" in payload_data:
215
- items = payload_data["workers"]
216
- else:
217
- items = [payload_data]
218
- elif isinstance(payload_data, list):
219
- items = payload_data
220
- else:
221
- logging.error("Swarm payload must be a JSON object or array.")
222
- return False
223
-
224
- if len(items) > MAX_WORKERS_PER_SWARM:
225
- logging.error(
226
- f"Swarm payload contains {len(items)} workers, "
227
- f"exceeding the maximum of {MAX_WORKERS_PER_SWARM}."
228
- )
229
- return False
230
-
231
- all_errors = []
232
- for i, item in enumerate(items):
233
- if not isinstance(item, dict):
234
- all_errors.append(f"Item[{i}]: must be a JSON object.")
235
- continue
236
-
237
- # Detect whether this is a WorkerRequest or WorkerResult by key presence
238
- if "status" in item and "output" in item:
239
- # Looks like a WorkerResult
240
- errors = validate_worker_result(item, i)
241
- else:
242
- # Treat as WorkerRequest
243
- errors = validate_worker_request(item, i, agents_dir)
244
-
245
- all_errors.extend(errors)
246
-
247
- if all_errors:
248
- for err in all_errors:
249
- logging.error(err)
250
- return False
251
-
252
- return True
253
-
254
-
255
- # ─── Main ─────────────────────────────────────────────────────────────────────
256
-
257
- def main():
258
- parser = argparse.ArgumentParser(
259
- description=(
260
- "Validate Orchestrator micro-worker payloads (legacy) "
261
- "and Swarm WorkerRequest/WorkerResult payloads (swarm mode)."
262
- )
263
- )
264
- parser.add_argument("--payload", type=str, help="JSON string of the payload", required=False)
265
- parser.add_argument("--file", type=str, help="Path to a JSON file containing the payload", required=False)
266
- parser.add_argument("--workspace", type=str, default=".", help="Workspace root directory")
267
- parser.add_argument(
268
- "--mode",
269
- type=str,
270
- choices=["legacy", "swarm"],
271
- default="legacy",
272
- help=(
273
- "Validation mode. "
274
- "'legacy': validate orchestrator dispatch_micro_workers payload (default). "
275
- "'swarm': validate WorkerRequest or WorkerResult JSON."
276
- )
277
- )
278
-
279
- args = parser.parse_args()
280
-
281
- if not args.payload and not args.file:
282
- logging.error("Must provide either --payload or --file")
283
- sys.exit(1)
284
-
285
- workspace_root = Path(args.workspace).resolve()
286
- agent_dir = find_agent_dir(workspace_root)
287
-
288
- if not agent_dir:
289
- logging.error(f"Could not find .agent directory starting from {workspace_root}")
290
- sys.exit(1)
291
-
292
- agents_dir = agent_dir / "agents"
293
- if not agents_dir.exists():
294
- logging.error(f"Could not find 'agents' directory inside {agent_dir}")
295
- sys.exit(1)
296
-
297
- try:
298
- if args.file:
299
- with open(args.file, 'r', encoding='utf-8') as f:
300
- payload_data = json.load(f)
301
- else:
302
- payload_data = json.loads(args.payload)
303
- except Exception as e:
304
- logging.error(f"Failed to parse payload as JSON: {e}")
305
- sys.exit(1)
306
-
307
- if args.mode == "swarm":
308
- if not validate_swarm_payload(payload_data, agents_dir):
309
- logging.error("Swarm payload validation failed.")
310
- sys.exit(1)
311
-
312
- import subprocess
313
- ast_context = ""
314
- try:
315
- res = subprocess.run(["python", "-m", "code_review_graph", "review-delta"], cwd=workspace_root, capture_output=True, text=True)
316
- if res.returncode == 0 and res.stdout.strip():
317
- ast_context = "\n\n[AST Blast Radius Context]:\n" + res.stdout.strip()
318
- except Exception as e:
319
- logging.warning(f"code-review-graph hook failed: {e}")
320
-
321
- if ast_context:
322
- items = payload_data.get("workers", payload_data) if isinstance(payload_data, dict) else payload_data
323
- if isinstance(items, list):
324
- for item in items:
325
- if "context" in item:
326
- item["context"] += ast_context
327
- elif isinstance(items, dict) and "context" in items:
328
- items["context"] += ast_context
329
-
330
- logging.info("Swarm payload validation successful.")
331
- # Re-emit the enriched payload for downstream
332
- if ast_context:
333
- print("--- ENRICHED SWARM PAYLOAD ---")
334
- print(json.dumps(payload_data, indent=2))
335
- else:
336
- # Legacy mode
337
- if not validate_payload(payload_data, workspace_root, agents_dir):
338
- logging.error("Payload validation failed.")
339
- sys.exit(1)
340
-
341
- logging.info("Payload validation successful.")
342
- prompts = build_worker_prompts(payload_data, workspace_root)
343
-
344
- for i, p in enumerate(prompts):
345
- print(f"\n[Worker {i+1} Ready]")
346
- print(p)
347
-
348
-
349
- if __name__ == "__main__":
350
- main()
@@ -1,192 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- test_runner.py — Standalone test runner for the Tribunal Agent Kit.
4
-
5
- Detects and runs the project's test framework:
6
- - npm test (Jest / Vitest / Mocha)
7
- - pytest (Python)
8
- - go test (Go)
9
-
10
- Usage:
11
- python .agent/scripts/test_runner.py .
12
- python .agent/scripts/test_runner.py . --coverage
13
- python .agent/scripts/test_runner.py . --watch
14
- python .agent/scripts/test_runner.py . --file src/utils.test.ts
15
- """
16
-
17
- import os
18
- import sys
19
- import subprocess
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
-
31
- def header(title: str) -> None:
32
- print(f"\n{BOLD}{BLUE}━━━ {title} ━━━{RESET}")
33
-
34
-
35
- def ok(msg: str) -> None:
36
- print(f" {GREEN}✅ {msg}{RESET}")
37
-
38
-
39
- def fail(msg: str) -> None:
40
- print(f" {RED}❌ {msg}{RESET}")
41
-
42
-
43
- def skip(msg: str) -> None:
44
- print(f" {YELLOW}⏭️ {msg}{RESET}")
45
-
46
-
47
- def run_tests(label: str, cmd: list[str], cwd: str) -> bool:
48
- """Run a test command, streaming output in real-time."""
49
- try:
50
- result = subprocess.run(
51
- cmd, cwd=cwd, capture_output=True, text=True, timeout=300
52
- )
53
- output = (result.stdout + result.stderr).strip()
54
- if output:
55
- for line in output.split("\n"):
56
- print(f" {line}")
57
-
58
- if result.returncode == 0:
59
- ok(f"{label} — all tests passed")
60
- return True
61
- fail(f"{label} — test failures detected")
62
- return False
63
- except FileNotFoundError:
64
- skip(f"{label} — tool not installed")
65
- return True
66
- except subprocess.TimeoutExpired:
67
- fail(f"{label} — timed out after 300s")
68
- return False
69
-
70
-
71
- def detect_test_framework(project_root: str) -> str | None:
72
- """Detect the primary test framework from project files."""
73
- root = Path(project_root)
74
-
75
- # Node.js test frameworks
76
- pkg_json = root / "package.json"
77
- if pkg_json.exists():
78
- try:
79
- import json
80
- with open(pkg_json) as f:
81
- pkg = json.load(f)
82
- deps = {**pkg.get("dependencies", {}), **pkg.get("devDependencies", {})}
83
- scripts = pkg.get("scripts", {})
84
-
85
- if "vitest" in deps:
86
- return "vitest"
87
- if "jest" in deps:
88
- return "jest"
89
- if "mocha" in deps:
90
- return "mocha"
91
- if "test" in scripts:
92
- return "npm-test"
93
- except Exception:
94
- if "test" in str(pkg_json.read_text()):
95
- return "npm-test"
96
-
97
- # Python
98
- if (root / "pytest.ini").exists() or (root / "pyproject.toml").exists() or (root / "conftest.py").exists():
99
- return "pytest"
100
-
101
- # Go
102
- go_files = list(root.glob("**/*_test.go"))
103
- if go_files:
104
- return "go"
105
-
106
- return None
107
-
108
-
109
- def main() -> None:
110
- parser = argparse.ArgumentParser(
111
- description="Tribunal test runner — detects and runs the project's test framework"
112
- )
113
- parser.add_argument("path", help="Project root directory")
114
- parser.add_argument("--coverage", action="store_true", help="Run tests with coverage reporting")
115
- parser.add_argument("--watch", action="store_true", help="Run tests in watch mode")
116
- parser.add_argument("--file", help="Run tests for a specific file only")
117
- args = parser.parse_args()
118
-
119
- project_root = os.path.abspath(args.path)
120
- if not os.path.isdir(project_root):
121
- fail(f"Directory not found: {project_root}")
122
- sys.exit(1)
123
-
124
- print(f"{BOLD}Tribunal — test_runner.py{RESET}")
125
- print(f"Project: {project_root}")
126
-
127
- framework = detect_test_framework(project_root)
128
- if not framework:
129
- skip("No test framework detected in this project")
130
- sys.exit(0)
131
-
132
- header(f"Running tests ({framework})")
133
-
134
- cmd: list[str] = []
135
- passed = True
136
-
137
- if framework in ("vitest", "jest", "mocha", "npm-test"):
138
- if framework == "vitest":
139
- cmd = ["npx", "vitest", "run"]
140
- if args.coverage:
141
- cmd.append("--coverage")
142
- if args.watch:
143
- cmd = ["npx", "vitest"] # watch is vitest default
144
- if args.file:
145
- cmd.append(args.file)
146
- elif framework == "jest":
147
- cmd = ["npx", "jest"]
148
- if args.coverage:
149
- cmd.append("--coverage")
150
- if args.watch:
151
- cmd.append("--watch")
152
- if args.file:
153
- cmd.append(args.file)
154
- else:
155
- cmd = ["npm", "test", "--", "--passWithNoTests"]
156
- if args.coverage:
157
- cmd.append("--coverage")
158
- if args.file:
159
- cmd.append(args.file)
160
- passed = run_tests(framework, cmd, project_root)
161
-
162
- elif framework == "pytest":
163
- cmd = ["python", "-m", "pytest", "-v"]
164
- if args.coverage:
165
- cmd.extend(["--cov", "--cov-report=term-missing"])
166
- if args.watch:
167
- cmd = ["python", "-m", "pytest-watch", "--", "-v"] # pytest-watch
168
-
169
- if args.file:
170
- cmd.append(args.file)
171
- passed = run_tests("pytest", cmd, project_root)
172
-
173
- elif framework == "go":
174
- cmd = ["go", "test", "./...", "-v"]
175
- if args.coverage:
176
- cmd.append("-cover")
177
- if args.file:
178
- cmd = ["go", "test", "-v", "-run", args.file]
179
- passed = run_tests("go test", cmd, project_root)
180
-
181
- # Summary
182
- print(f"\n{BOLD}━━━ Test Summary ━━━{RESET}")
183
- if passed:
184
- ok(f"Tests passed ({framework})")
185
- else:
186
- fail(f"Tests failed ({framework})")
187
-
188
- sys.exit(0 if passed else 1)
189
-
190
-
191
- if __name__ == "__main__":
192
- main()
@@ -1,163 +0,0 @@
1
- import pytest
2
- from pathlib import Path
3
-
4
- # Adjust the path so we can import swarm_dispatcher directly
5
- import sys
6
- import os
7
- sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
8
-
9
- from swarm_dispatcher import find_agent_dir, validate_payload, build_worker_prompts
10
-
11
-
12
- def test_find_agent_dir_found(tmp_path):
13
- # Setup: Create an .agent directory and a deep subdirectory
14
- agent_dir = tmp_path / ".agent"
15
- agent_dir.mkdir()
16
- sub_dir = tmp_path / "src" / "deep" / "folder"
17
- sub_dir.mkdir(parents=True)
18
-
19
- # Act & Assert
20
- result = find_agent_dir(sub_dir)
21
- assert result is not None
22
- assert result.resolve() == agent_dir.resolve()
23
-
24
- def test_find_agent_dir_not_found(tmp_path):
25
- # Setup: Directory structure without '.agent'
26
- sub_dir = tmp_path / "src" / "deep"
27
- sub_dir.mkdir(parents=True)
28
-
29
- # Act & Assert
30
- assert find_agent_dir(sub_dir) is None
31
-
32
- def test_validate_payload_valid(tmp_path):
33
- # Setup
34
- agents_dir = tmp_path / "agents"
35
- agents_dir.mkdir()
36
- agent_file = agents_dir / "test_agent.md"
37
- agent_file.touch()
38
-
39
- workspace = tmp_path / "workspace"
40
- workspace.mkdir()
41
- file_attached = workspace / "file1.txt"
42
- file_attached.touch()
43
-
44
- payload = {
45
- "dispatch_micro_workers": [
46
- {
47
- "target_agent": "test_agent",
48
- "files_attached": ["file1.txt"]
49
- }
50
- ]
51
- }
52
-
53
- # Act & Assert
54
- assert validate_payload(payload, workspace, agents_dir) is True
55
-
56
- def test_validate_payload_missing_workers(tmp_path):
57
- # Act & Assert
58
- assert validate_payload({}, tmp_path, tmp_path) is False
59
-
60
- def test_validate_payload_not_list(tmp_path):
61
- # Act & Assert
62
- assert validate_payload({"dispatch_micro_workers": "str"}, tmp_path, tmp_path) is False
63
-
64
- def test_validate_payload_missing_target_agent(tmp_path):
65
- # Setup
66
- payload = {
67
- "dispatch_micro_workers": [
68
- {
69
- "files_attached": []
70
- }
71
- ]
72
- }
73
-
74
- # Act & Assert
75
- assert validate_payload(payload, tmp_path, tmp_path) is False
76
-
77
- def test_validate_payload_agent_not_found(tmp_path):
78
- # Setup
79
- agents_dir = tmp_path / "agents"
80
- agents_dir.mkdir()
81
-
82
- payload = {
83
- "dispatch_micro_workers": [
84
- {
85
- "target_agent": "missing_agent"
86
- }
87
- ]
88
- }
89
-
90
- # Act & Assert
91
- assert validate_payload(payload, tmp_path, agents_dir) is False
92
-
93
- def test_validate_payload_files_not_a_list(tmp_path):
94
- agents_dir = tmp_path / "agents"
95
- agents_dir.mkdir()
96
- (agents_dir / "test_agent.md").touch()
97
-
98
- payload = {
99
- "dispatch_micro_workers": [
100
- {
101
- "target_agent": "test_agent",
102
- "files_attached": "a single file string"
103
- }
104
- ]
105
- }
106
-
107
- assert validate_payload(payload, tmp_path, agents_dir) is False
108
-
109
- def test_validate_payload_files_missing_warning(tmp_path, caplog):
110
- # Tests that the validation still passes if files are missing,
111
- # but that a warning is logged.
112
- agents_dir = tmp_path / "agents"
113
- agents_dir.mkdir()
114
- (agents_dir / "test_agent.md").touch()
115
-
116
- workspace = tmp_path / "workspace"
117
- workspace.mkdir()
118
-
119
- payload = {
120
- "dispatch_micro_workers": [
121
- {
122
- "target_agent": "test_agent",
123
- "files_attached": ["nonexistent.txt"]
124
- }
125
- ]
126
- }
127
-
128
- assert validate_payload(payload, workspace, agents_dir) is True
129
- assert "attached file 'nonexistent.txt' does not exist" in caplog.text
130
-
131
- def test_build_worker_prompts():
132
- payload = {
133
- "dispatch_micro_workers": [
134
- {
135
- "target_agent": "worker1",
136
- "context_summary": "Initial context value",
137
- "task_description": "First task to do",
138
- "files_attached": ["main.py", "utils.py"]
139
- },
140
- {
141
- "target_agent": "worker2",
142
- "context_summary": "Another context",
143
- "task_description": "Second task",
144
- "files_attached": []
145
- }
146
- ]
147
- }
148
-
149
- prompts = build_worker_prompts(payload, Path("."))
150
-
151
- assert len(prompts) == 2
152
-
153
- # Check first prompt
154
- assert "Agent: worker1" in prompts[0]
155
- assert "Context: Initial context value" in prompts[0]
156
- assert "Task: First task to do" in prompts[0]
157
- assert "Attached Files: main.py, utils.py" in prompts[0]
158
-
159
- # Check second prompt
160
- assert "Agent: worker2" in prompts[1]
161
- assert "Context: Another context" in prompts[1]
162
- assert "Task: Second task" in prompts[1]
163
- assert "Attached Files: None" in prompts[1]