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,180 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- auto_preview.py — Start, stop, or check a local development server.
4
-
5
- Usage:
6
- python .agent/scripts/auto_preview.py start
7
- python .agent/scripts/auto_preview.py stop
8
- python .agent/scripts/auto_preview.py status
9
- python .agent/scripts/auto_preview.py restart
10
- """
11
-
12
- import os
13
- import sys
14
- import json
15
- import time
16
- import signal
17
- import socket
18
- import subprocess
19
- from pathlib import Path
20
-
21
- PID_FILE = ".preview.pid"
22
- DEFAULT_PORT = 3000
23
- TIMEOUT_SECONDS = 30
24
-
25
- GREEN = "\033[92m"
26
- RED = "\033[91m"
27
- YELLOW = "\033[93m"
28
- BOLD = "\033[1m"
29
- RESET = "\033[0m"
30
-
31
-
32
- def find_start_command() -> tuple[list[str], bool]:
33
- """
34
- Read package.json for a dev/start script.
35
- Returns (command, found) — found=False if no package.json or no scripts.
36
- """
37
- pkg_path = Path("package.json")
38
- if not pkg_path.exists():
39
- return [], False
40
-
41
- try:
42
- with open(pkg_path) as f:
43
- pkg = json.load(f)
44
- scripts = pkg.get("scripts", {})
45
- if "dev" in scripts:
46
- return ["npm", "run", "dev"], True
47
- elif "start" in scripts:
48
- return ["npm", "run", "start"], True
49
- else:
50
- return [], False
51
- except (json.JSONDecodeError, IOError):
52
- return [], False
53
-
54
-
55
- def get_port_from_env() -> int:
56
- env_path = Path(".env")
57
- if env_path.exists():
58
- try:
59
- with open(env_path) as f:
60
- for line in f:
61
- if line.startswith("PORT="):
62
- return int(line.split("=")[1].strip())
63
- except (ValueError, IOError):
64
- pass
65
- return DEFAULT_PORT
66
-
67
-
68
- def is_port_open(port: int) -> bool:
69
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
70
- s.settimeout(1)
71
- return s.connect_ex(("localhost", port)) == 0
72
-
73
-
74
- def read_pid() -> int | None:
75
- pid_file = Path(PID_FILE)
76
- if pid_file.exists():
77
- try:
78
- return int(pid_file.read_text().strip())
79
- except (ValueError, IOError):
80
- pass
81
- return None
82
-
83
-
84
- def write_pid(pid: int) -> None:
85
- Path(PID_FILE).write_text(str(pid))
86
-
87
-
88
- def clear_pid() -> None:
89
- pid_file = Path(PID_FILE)
90
- if pid_file.exists():
91
- pid_file.unlink()
92
-
93
-
94
- def start_server() -> None:
95
- port = get_port_from_env()
96
-
97
- if is_port_open(port):
98
- print(f"{YELLOW}⚠️ Port {port} is already in use.{RESET}")
99
- pid = read_pid()
100
- if pid:
101
- print(f" Known PID: {pid}")
102
- return
103
-
104
- cmd, found = find_start_command()
105
- if not found:
106
- print(f"{RED}❌ No dev/start script found.{RESET}")
107
- print(f" This project has no package.json, or its package.json has no 'dev' or 'start' script.")
108
- print(f" Add a script to package.json, or start your server manually.")
109
- return
110
-
111
- print(f"{BOLD}Starting: {' '.join(cmd)}{RESET}")
112
- proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
113
- write_pid(proc.pid)
114
-
115
- print(f"Waiting for port {port}…", end="", flush=True)
116
- for _ in range(TIMEOUT_SECONDS):
117
- if is_port_open(port):
118
- print()
119
- print(f"\n{GREEN}✅ Server started{RESET}")
120
- print(f" URL: http://localhost:{port}")
121
- print(f" PID: {proc.pid}")
122
- print(f" Command: {' '.join(cmd)}")
123
- print(f"\nStop with: python .agent/scripts/auto_preview.py stop")
124
- return
125
- print(".", end="", flush=True)
126
- time.sleep(1)
127
-
128
- print()
129
- print(f"{RED}❌ Server did not start within {TIMEOUT_SECONDS}s{RESET}")
130
- proc.terminate()
131
- clear_pid()
132
-
133
-
134
- def stop_server() -> None:
135
- pid = read_pid()
136
- if not pid:
137
- print(f"{YELLOW}⚠️ No stored server PID found{RESET}")
138
- return
139
- try:
140
- os.kill(pid, signal.SIGTERM)
141
- time.sleep(1)
142
- print(f"{GREEN}✅ Server stopped (PID {pid}){RESET}")
143
- except ProcessLookupError:
144
- print(f"{YELLOW}Process {pid} was not running{RESET}")
145
- finally:
146
- clear_pid()
147
-
148
-
149
- def show_status() -> None:
150
- port = get_port_from_env()
151
- pid = read_pid()
152
- if is_port_open(port):
153
- print(f"{GREEN}🟢 Running — http://localhost:{port}{RESET}")
154
- if pid:
155
- print(f" PID: {pid}")
156
- else:
157
- print(f"{RED}🔴 Not running on port {port}{RESET}")
158
-
159
-
160
- def main() -> None:
161
- actions = {"start", "stop", "status", "restart"}
162
- if len(sys.argv) < 2 or sys.argv[1] not in actions:
163
- print(f"Usage: auto_preview.py [start|stop|status|restart]")
164
- sys.exit(1)
165
-
166
- action = sys.argv[1]
167
- if action == "start":
168
- start_server()
169
- elif action == "stop":
170
- stop_server()
171
- elif action == "status":
172
- show_status()
173
- elif action == "restart":
174
- stop_server()
175
- time.sleep(1)
176
- start_server()
177
-
178
-
179
- if __name__ == "__main__":
180
- main()
@@ -1,259 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- bundle_analyzer.py — JS/TS bundle size analyzer for the Tribunal Agent Kit.
4
-
5
- Analyzes build output for:
6
- - Total bundle size
7
- - Largest files in dist/
8
- - Suggested tree-shaking opportunities
9
- - Bundler-specific analysis (Vite / Webpack)
10
-
11
- Usage:
12
- python .agent/scripts/bundle_analyzer.py .
13
- python .agent/scripts/bundle_analyzer.py . --build
14
- python .agent/scripts/bundle_analyzer.py . --threshold 500
15
- """
16
-
17
- import os
18
- import sys
19
- import json
20
- import subprocess
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
- # Common large dependencies that often have lighter alternatives
32
- HEAVY_PACKAGES: dict[str, str] = {
33
- "moment": "Use date-fns or dayjs instead (~2KB vs ~230KB)",
34
- "lodash": "Import specific functions: lodash/debounce instead of full lodash",
35
- "rxjs": "Import specific operators to enable tree-shaking",
36
- "aws-sdk": "Use @aws-sdk/client-* v3 modular imports",
37
- "firebase": "Use modular imports: firebase/auth, firebase/firestore",
38
- "chart.js": "Register only needed components",
39
- "three": "Import specific modules from three/examples/jsm/",
40
- "@mui/material": "Ensure babel-plugin-import or modular imports",
41
- "@mui/icons-material": "Import specific icons, never the barrel",
42
- "antd": "Use modular imports with babel-plugin-import",
43
- }
44
-
45
-
46
- def header(title: str) -> None:
47
- print(f"\n{BOLD}{BLUE}━━━ {title} ━━━{RESET}")
48
-
49
-
50
- def ok(msg: str) -> None:
51
- print(f" {GREEN}✅ {msg}{RESET}")
52
-
53
-
54
- def fail(msg: str) -> None:
55
- print(f" {RED}❌ {msg}{RESET}")
56
-
57
-
58
- def warn(msg: str) -> None:
59
- print(f" {YELLOW}⚠️ {msg}{RESET}")
60
-
61
-
62
- def skip(msg: str) -> None:
63
- print(f" {YELLOW}⏭️ {msg}{RESET}")
64
-
65
-
66
- def format_size(size_bytes: int) -> str:
67
- """Format bytes into human-readable size."""
68
- if size_bytes < 1024:
69
- return f"{size_bytes}B"
70
- elif size_bytes < 1024 * 1024:
71
- return f"{size_bytes / 1024:.1f}KB"
72
- else:
73
- return f"{size_bytes / (1024 * 1024):.1f}MB"
74
-
75
-
76
- def detect_bundler(project_root: str) -> str | None:
77
- """Detect the bundler used in the project."""
78
- root = Path(project_root)
79
- pkg_path = root / "package.json"
80
- if not pkg_path.exists():
81
- return None
82
-
83
- try:
84
- with open(pkg_path) as f:
85
- pkg = json.load(f)
86
- deps = {**pkg.get("dependencies", {}), **pkg.get("devDependencies", {})}
87
-
88
- if "vite" in deps:
89
- return "vite"
90
- if "next" in deps:
91
- return "next"
92
- if "webpack" in deps:
93
- return "webpack"
94
- if any(f.exists() for f in [root / "webpack.config.js", root / "webpack.config.ts"]):
95
- return "webpack"
96
- except (json.JSONDecodeError, IOError):
97
- pass
98
-
99
- return None
100
-
101
-
102
- def find_dist_dir(project_root: str) -> str | None:
103
- """Find the build output directory."""
104
- root = Path(project_root)
105
- candidates = ["dist", "build", ".next", "out", "public/build"]
106
- for candidate in candidates:
107
- d = root / candidate
108
- if d.is_dir():
109
- return str(d)
110
- return None
111
-
112
-
113
- def analyze_dist(dist_dir: str, threshold_kb: int) -> tuple[int, list[tuple[str, int]]]:
114
- """Analyze the dist directory. Returns (total_size, list of (file, size) sorted by size desc)."""
115
- files: list[tuple[str, int]] = []
116
- total = 0
117
-
118
- for root, dirs, filenames in os.walk(dist_dir):
119
- for fname in filenames:
120
- fpath = os.path.join(root, fname)
121
- size = os.path.getsize(fpath)
122
- total += size
123
- rel = os.path.relpath(fpath, dist_dir)
124
- files.append((rel, size))
125
-
126
- files.sort(key=lambda x: x[1], reverse=True)
127
- return total, files
128
-
129
-
130
- def check_heavy_dependencies(project_root: str) -> list[tuple[str, str]]:
131
- """Check if any known-heavy packages are in dependencies."""
132
- pkg_path = Path(project_root) / "package.json"
133
- if not pkg_path.exists():
134
- return []
135
-
136
- try:
137
- with open(pkg_path) as f:
138
- pkg = json.load(f)
139
- deps = set(pkg.get("dependencies", {}).keys())
140
- found: list[tuple[str, str]] = []
141
- for pkg_name, suggestion in HEAVY_PACKAGES.items():
142
- if pkg_name in deps:
143
- found.append((pkg_name, suggestion))
144
- return found
145
- except (json.JSONDecodeError, IOError):
146
- return []
147
-
148
-
149
- def run_build(project_root: str) -> bool:
150
- """Run npm run build."""
151
- try:
152
- result = subprocess.run(
153
- ["npm", "run", "build"],
154
- cwd=project_root,
155
- capture_output=True,
156
- text=True,
157
- timeout=120,
158
- )
159
- if result.returncode == 0:
160
- ok("Build completed successfully")
161
- return True
162
- fail("Build failed")
163
- output = (result.stdout + result.stderr).strip()
164
- if output:
165
- for line in output.split("\n")[:10]:
166
- print(f" {line}")
167
- return False
168
- except FileNotFoundError:
169
- fail("npm not installed")
170
- return False
171
- except subprocess.TimeoutExpired:
172
- fail("Build timed out after 120s")
173
- return False
174
-
175
-
176
- def main() -> None:
177
- parser = argparse.ArgumentParser(
178
- description="Tribunal bundle analyzer — checks build output size and suggests optimizations"
179
- )
180
- parser.add_argument("path", help="Project root directory")
181
- parser.add_argument("--build", action="store_true", help="Run npm run build before analyzing")
182
- parser.add_argument("--threshold", type=int, default=250, help="File size warning threshold in KB (default: 250)")
183
- args = parser.parse_args()
184
-
185
- project_root = os.path.abspath(args.path)
186
- if not os.path.isdir(project_root):
187
- fail(f"Directory not found: {project_root}")
188
- sys.exit(1)
189
-
190
- print(f"{BOLD}Tribunal — bundle_analyzer.py{RESET}")
191
- print(f"Project: {project_root}")
192
-
193
- bundler = detect_bundler(project_root)
194
- if bundler:
195
- print(f" Bundler: {bundler}")
196
-
197
- # Optionally build first
198
- if args.build:
199
- header("Building project")
200
- if not run_build(project_root):
201
- sys.exit(1)
202
-
203
- # Find and analyze dist directory
204
- dist_dir = find_dist_dir(project_root)
205
- if not dist_dir:
206
- skip("No build output directory found (dist/, build/, .next/, out/)")
207
- skip("Run with --build to create a build first, or build manually")
208
- else:
209
- header(f"Bundle Size Analysis ({os.path.relpath(dist_dir, project_root)}/)")
210
-
211
- total_size, files = analyze_dist(dist_dir, args.threshold)
212
- print(f"\n Total bundle size: {BOLD}{format_size(total_size)}{RESET}")
213
-
214
- threshold_bytes = args.threshold * 1024
215
-
216
- # Show top 10 largest files
217
- print(f"\n {BOLD}Top files by size:{RESET}")
218
- for filepath, size in files[:10]:
219
- if size > threshold_bytes:
220
- warn(f"{format_size(size):>10s} {filepath}")
221
- else:
222
- print(f" {'':>4s}{format_size(size):>10s} {filepath}")
223
-
224
- # Count JS/CSS files above threshold
225
- large_js = [(f, s) for f, s in files if f.endswith((".js", ".mjs")) and s > threshold_bytes]
226
- if large_js:
227
- print(f"\n {YELLOW}{len(large_js)} JS file(s) exceed {args.threshold}KB threshold{RESET}")
228
-
229
- # Check for heavy dependencies
230
- header("Dependency Weight Check")
231
- heavy = check_heavy_dependencies(project_root)
232
- if heavy:
233
- for pkg_name, suggestion in heavy:
234
- warn(f"'{pkg_name}' is a heavy dependency")
235
- print(f" → {suggestion}")
236
- else:
237
- ok("No known-heavy packages detected")
238
-
239
- # Summary
240
- print(f"\n{BOLD}━━━ Bundle Analysis Summary ━━━{RESET}")
241
- if dist_dir:
242
- total_size_val = analyze_dist(dist_dir, args.threshold)[0]
243
- size_str = format_size(total_size_val)
244
- if total_size_val > 5 * 1024 * 1024:
245
- fail(f"Total bundle: {size_str} — consider code splitting")
246
- elif total_size_val > 2 * 1024 * 1024:
247
- warn(f"Total bundle: {size_str} — review for optimization opportunities")
248
- else:
249
- ok(f"Total bundle: {size_str}")
250
- if heavy:
251
- warn(f"{len(heavy)} heavy dependency suggestion(s) — see above")
252
- elif not heavy and dist_dir:
253
- ok("No optimization suggestions")
254
-
255
- sys.exit(0)
256
-
257
-
258
- if __name__ == "__main__":
259
- main()