tribunal-kit 4.0.1 → 4.3.0
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/ARCHITECTURE.md +21 -14
- package/.agent/GEMINI.md +4 -2
- package/.agent/agents/api-architect.md +66 -0
- package/.agent/agents/db-latency-auditor.md +216 -0
- package/.agent/agents/precedence-reviewer.md +41 -4
- package/.agent/agents/resilience-reviewer.md +88 -0
- package/.agent/agents/schema-reviewer.md +67 -0
- package/.agent/agents/swarm-worker-contracts.md +5 -5
- package/.agent/agents/throughput-optimizer.md +299 -0
- package/.agent/agents/ui-ux-auditor.md +292 -0
- package/.agent/agents/vitals-reviewer.md +223 -0
- package/.agent/history/case-law/cases/case-0001.json +33 -0
- package/.agent/history/case-law/index.json +35 -0
- package/.agent/rules/GEMINI.md +28 -11
- package/.agent/scripts/__pycache__/_colors.cpython-311.pyc +0 -0
- package/.agent/scripts/__pycache__/_utils.cpython-311.pyc +0 -0
- package/.agent/scripts/__pycache__/case_law_manager.cpython-311.pyc +0 -0
- package/.agent/scripts/_colors.js +18 -0
- package/.agent/scripts/_utils.js +42 -0
- package/.agent/scripts/auto_preview.js +197 -0
- package/.agent/scripts/bundle_analyzer.js +290 -0
- package/.agent/scripts/case_law_manager.js +684 -0
- package/.agent/scripts/checklist.js +266 -0
- package/.agent/scripts/colors.js +17 -0
- package/.agent/scripts/compress_skills.js +141 -0
- package/.agent/scripts/consolidate_skills.js +149 -0
- package/.agent/scripts/context_broker.js +609 -0
- package/.agent/scripts/deep_compress.js +150 -0
- package/.agent/scripts/dependency_analyzer.js +272 -0
- package/.agent/scripts/inner_loop_validator.js +465 -0
- package/.agent/scripts/lint_runner.js +187 -0
- package/.agent/scripts/minify_context.js +100 -0
- package/.agent/scripts/patch_skills_meta.js +156 -0
- package/.agent/scripts/patch_skills_output.js +244 -0
- package/.agent/scripts/schema_validator.js +297 -0
- package/.agent/scripts/security_scan.js +303 -0
- package/.agent/scripts/session_manager.js +276 -0
- package/.agent/scripts/skill_evolution.js +644 -0
- package/.agent/scripts/skill_integrator.js +313 -0
- package/.agent/scripts/strengthen_skills.js +193 -0
- package/.agent/scripts/strip_tribunal.js +47 -0
- package/.agent/scripts/swarm_dispatcher.js +360 -0
- package/.agent/scripts/test_runner.js +193 -0
- package/.agent/scripts/utils.js +32 -0
- package/.agent/scripts/verify_all.js +256 -0
- package/.agent/skills/agent-organizer/SKILL.md +42 -0
- package/.agent/skills/agentic-patterns/SKILL.md +42 -0
- package/.agent/skills/ai-prompt-injection-defense/SKILL.md +42 -0
- package/.agent/skills/api-patterns/SKILL.md +42 -0
- package/.agent/skills/api-security-auditor/SKILL.md +42 -0
- package/.agent/skills/app-builder/SKILL.md +42 -0
- package/.agent/skills/app-builder/templates/SKILL.md +70 -0
- package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +1 -1
- package/.agent/skills/appflow-wireframe/SKILL.md +42 -0
- package/.agent/skills/architecture/SKILL.md +42 -0
- package/.agent/skills/authentication-best-practices/SKILL.md +42 -0
- package/.agent/skills/bash-linux/SKILL.md +42 -0
- package/.agent/skills/behavioral-modes/SKILL.md +42 -0
- package/.agent/skills/brainstorming/SKILL.md +42 -0
- package/.agent/skills/building-native-ui/SKILL.md +42 -0
- package/.agent/skills/clean-code/SKILL.md +42 -0
- package/.agent/skills/code-review-checklist/SKILL.md +42 -0
- package/.agent/skills/config-validator/SKILL.md +42 -0
- package/.agent/skills/csharp-developer/SKILL.md +42 -0
- package/.agent/skills/data-validation-schemas/SKILL.md +320 -0
- package/.agent/skills/database-design/SKILL.md +42 -0
- package/.agent/skills/deployment-procedures/SKILL.md +42 -0
- package/.agent/skills/devops-engineer/SKILL.md +42 -0
- package/.agent/skills/devops-incident-responder/SKILL.md +42 -0
- package/.agent/skills/doc.md +1 -1
- package/.agent/skills/documentation-templates/SKILL.md +42 -0
- package/.agent/skills/edge-computing/SKILL.md +42 -0
- package/.agent/skills/error-resilience/SKILL.md +420 -0
- package/.agent/skills/extract-design-system/SKILL.md +42 -0
- package/.agent/skills/framer-motion-expert/SKILL.md +42 -1
- package/.agent/skills/frontend-design/SKILL.md +42 -0
- package/.agent/skills/game-design-expert/SKILL.md +42 -0
- package/.agent/skills/game-engineering-expert/SKILL.md +42 -0
- package/.agent/skills/geo-fundamentals/SKILL.md +42 -0
- package/.agent/skills/github-operations/SKILL.md +42 -0
- package/.agent/skills/gsap-core/SKILL.md +300 -0
- package/.agent/skills/gsap-frameworks/SKILL.md +199 -0
- package/.agent/skills/gsap-performance/SKILL.md +125 -0
- package/.agent/skills/gsap-plugins/SKILL.md +472 -0
- package/.agent/skills/gsap-react/SKILL.md +181 -0
- package/.agent/skills/gsap-scrolltrigger/SKILL.md +342 -0
- package/.agent/skills/gsap-timeline/SKILL.md +153 -0
- package/.agent/skills/gsap-utils/SKILL.md +330 -0
- package/.agent/skills/i18n-localization/SKILL.md +42 -0
- package/.agent/skills/intelligent-routing/SKILL.md +72 -1
- package/.agent/skills/lint-and-validate/SKILL.md +42 -0
- package/.agent/skills/llm-engineering/SKILL.md +42 -0
- package/.agent/skills/local-first/SKILL.md +42 -0
- package/.agent/skills/mcp-builder/SKILL.md +42 -0
- package/.agent/skills/mobile-design/SKILL.md +42 -0
- package/.agent/skills/monorepo-management/SKILL.md +326 -0
- package/.agent/skills/motion-engineering/SKILL.md +42 -0
- package/.agent/skills/nextjs-react-expert/SKILL.md +42 -0
- package/.agent/skills/nodejs-best-practices/SKILL.md +42 -0
- package/.agent/skills/observability/SKILL.md +42 -0
- package/.agent/skills/parallel-agents/SKILL.md +42 -0
- package/.agent/skills/performance-profiling/SKILL.md +42 -0
- package/.agent/skills/plan-writing/SKILL.md +42 -0
- package/.agent/skills/platform-engineer/SKILL.md +42 -0
- package/.agent/skills/playwright-best-practices/SKILL.md +42 -0
- package/.agent/skills/powershell-windows/SKILL.md +42 -0
- package/.agent/skills/project-idioms/SKILL.md +42 -0
- package/.agent/skills/python-patterns/SKILL.md +42 -0
- package/.agent/skills/python-pro/SKILL.md +42 -0
- package/.agent/skills/react-specialist/SKILL.md +42 -0
- package/.agent/skills/readme-builder/SKILL.md +42 -0
- package/.agent/skills/realtime-patterns/SKILL.md +42 -0
- package/.agent/skills/red-team-tactics/SKILL.md +42 -0
- package/.agent/skills/rust-pro/SKILL.md +42 -0
- package/.agent/skills/seo-fundamentals/SKILL.md +42 -0
- package/.agent/skills/server-management/SKILL.md +42 -0
- package/.agent/skills/shadcn-ui-expert/SKILL.md +42 -0
- package/.agent/skills/skill-creator/SKILL.md +42 -0
- package/.agent/skills/sql-pro/SKILL.md +42 -0
- package/.agent/skills/supabase-postgres-best-practices/SKILL.md +42 -0
- package/.agent/skills/swiftui-expert/SKILL.md +42 -0
- package/.agent/skills/systematic-debugging/SKILL.md +42 -0
- package/.agent/skills/tailwind-patterns/SKILL.md +42 -0
- package/.agent/skills/tdd-workflow/SKILL.md +42 -0
- package/.agent/skills/test-result-analyzer/SKILL.md +42 -0
- package/.agent/skills/testing-patterns/SKILL.md +42 -0
- package/.agent/skills/trend-researcher/SKILL.md +42 -0
- package/.agent/skills/typescript-advanced/SKILL.md +327 -0
- package/.agent/skills/ui-ux-pro-max/SKILL.md +42 -0
- package/.agent/skills/ui-ux-researcher/SKILL.md +42 -0
- package/.agent/skills/vue-expert/SKILL.md +42 -0
- package/.agent/skills/vulnerability-scanner/SKILL.md +42 -0
- package/.agent/skills/web-accessibility-auditor/SKILL.md +42 -0
- package/.agent/skills/web-design-guidelines/SKILL.md +42 -0
- package/.agent/skills/webapp-testing/SKILL.md +42 -0
- package/.agent/skills/whimsy-injector/SKILL.md +42 -0
- package/.agent/skills/workflow-optimizer/SKILL.md +42 -0
- package/.agent/workflows/audit.md +6 -6
- package/.agent/workflows/deploy.md +1 -1
- package/.agent/workflows/generate.md +23 -6
- package/.agent/workflows/session.md +5 -5
- package/.agent/workflows/swarm.md +2 -2
- package/.agent/workflows/tribunal-backend.md +13 -2
- package/.agent/workflows/tribunal-full.md +15 -8
- package/.agent/workflows/tribunal-speed.md +183 -0
- package/README.md +64 -8
- package/bin/tribunal-kit.js +281 -41
- package/package.json +9 -6
- package/scripts/changelog.js +167 -0
- package/scripts/sync-version.js +81 -0
- package/.agent/scripts/__pycache__/auto_preview.cpython-311.pyc +0 -0
- package/.agent/scripts/__pycache__/bundle_analyzer.cpython-311.pyc +0 -0
- package/.agent/scripts/__pycache__/checklist.cpython-311.pyc +0 -0
- package/.agent/scripts/__pycache__/dependency_analyzer.cpython-311.pyc +0 -0
- package/.agent/scripts/__pycache__/security_scan.cpython-311.pyc +0 -0
- package/.agent/scripts/__pycache__/session_manager.cpython-311.pyc +0 -0
- package/.agent/scripts/__pycache__/skill_integrator.cpython-311.pyc +0 -0
- package/.agent/scripts/__pycache__/swarm_dispatcher.cpython-311.pyc +0 -0
- package/.agent/scripts/__pycache__/test_runner.cpython-311.pyc +0 -0
- package/.agent/scripts/__pycache__/verify_all.cpython-311.pyc +0 -0
- package/.agent/scripts/auto_preview.py +0 -180
- package/.agent/scripts/bundle_analyzer.py +0 -259
- package/.agent/scripts/case_law_manager.py +0 -525
- package/.agent/scripts/checklist.py +0 -209
- package/.agent/scripts/compress_skills.py +0 -167
- package/.agent/scripts/consolidate_skills.py +0 -173
- package/.agent/scripts/deep_compress.py +0 -202
- package/.agent/scripts/dependency_analyzer.py +0 -247
- package/.agent/scripts/lint_runner.py +0 -188
- package/.agent/scripts/minify_context.py +0 -80
- package/.agent/scripts/patch_skills_meta.py +0 -177
- package/.agent/scripts/patch_skills_output.py +0 -285
- package/.agent/scripts/schema_validator.py +0 -279
- package/.agent/scripts/security_scan.py +0 -224
- package/.agent/scripts/session_manager.py +0 -261
- package/.agent/scripts/skill_evolution.py +0 -563
- package/.agent/scripts/skill_integrator.py +0 -234
- package/.agent/scripts/strengthen_skills.py +0 -220
- package/.agent/scripts/strip_tribunal.py +0 -41
- package/.agent/scripts/swarm_dispatcher.py +0 -350
- package/.agent/scripts/test_runner.py +0 -192
- package/.agent/scripts/test_swarm_dispatcher.py +0 -163
- package/.agent/scripts/verify_all.py +0 -195
- package/.agent/skills/gsap-expert/SKILL.md +0 -194
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: vitals-reviewer
|
|
3
|
+
description: Frontend Core Web Vitals specialist. Audits React/Next.js/CSS code for INP violations, LCP blockers, CLS triggers, paint jank from View Transitions API misuse, Suspense waterfall patterns, render-blocking fonts, non-passive event listeners, and missing content-visibility optimizations. Token-scoped to UI files only (.tsx/.jsx/.css). Activates on /tribunal-speed and /tribunal-full.
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
last-updated: 2026-04-13
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Vitals Reviewer — Frontend Performance Specialist
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Core Mandate
|
|
13
|
+
|
|
14
|
+
You audit **frontend files only** — `.tsx`, `.jsx`, `.css`, `.module.css`. You never read server-side files, SQL, or ORM code. Your single goal: ensure every UI file meets 2026 Core Web Vitals targets. Every finding maps to a specific CWV metric.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Token Scope (MANDATORY)
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
✅ Activate on: **/*.tsx, **/*.jsx, **/*.css, **/*.module.css
|
|
22
|
+
❌ Skip entirely: **/*.sql, **/api/**, **/server/**, schema.prisma, *.test.*
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
This scope is non-negotiable. If a file doesn't match, return `N/A — outside vitals-reviewer scope`.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 2026 CWV Targets
|
|
30
|
+
|
|
31
|
+
|Metric|Good|Needs Work|Poor (❌ REJECTED)|
|
|
32
|
+
|:---|:---|:---|:---|
|
|
33
|
+
|**INP** Interaction to Next Paint|< 200ms|200–500ms|> 500ms|
|
|
34
|
+
|**LCP** Largest Contentful Paint|< 2.5s|2.5–4.0s|> 4.0s|
|
|
35
|
+
|**CLS** Cumulative Layout Shift|< 0.1|0.1–0.25|> 0.25|
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Section 1: LCP Audit Patterns
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
// ❌ LCP DAMAGE: Hero image without priority — browser discovers it late
|
|
43
|
+
<img src="/hero.jpg" />
|
|
44
|
+
|
|
45
|
+
// ❌ LCP DAMAGE: Raw @font-face without font-display — invisible text flash
|
|
46
|
+
@font-face {
|
|
47
|
+
font-family: 'Brand';
|
|
48
|
+
src: url('/brand.woff2');
|
|
49
|
+
/* Missing: font-display: swap; */
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ✅ APPROVED: next/image with priority on above-fold content
|
|
53
|
+
<Image src="/hero.jpg" priority={true} sizes="100vw" width={1920} height={1080} alt="Hero" />
|
|
54
|
+
|
|
55
|
+
// ✅ APPROVED: next/font eliminates render-blocking entirely
|
|
56
|
+
import { Inter } from 'next/font/google';
|
|
57
|
+
const inter = Inter({ subsets: ['latin'], display: 'swap' });
|
|
58
|
+
|
|
59
|
+
// ❌ LCP DAMAGE: Large SVG inlined in JSX (forces full parse before paint)
|
|
60
|
+
// Flag SVGs > 5KB inlined in component render — suggest external .svg file
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Section 2: INP Audit Patterns
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
// ❌ INP DAMAGE: Synchronous heavy computation on click
|
|
69
|
+
function handleSearch(query: string) {
|
|
70
|
+
const results = filterAllRecords(data, query); // Blocks main thread
|
|
71
|
+
setResults(results);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ✅ APPROVED: useTransition defers expensive state update
|
|
75
|
+
const [isPending, startTransition] = useTransition();
|
|
76
|
+
function handleSearch(query: string) {
|
|
77
|
+
startTransition(() => setResults(filterAllRecords(data, query)));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ❌ INP DAMAGE: Non-passive scroll listener (blocks scroll painting)
|
|
81
|
+
element.addEventListener('scroll', handler); // Missing { passive: true }
|
|
82
|
+
|
|
83
|
+
// ✅ APPROVED: Passive listener — browser paints immediately
|
|
84
|
+
element.addEventListener('scroll', handler, { passive: true });
|
|
85
|
+
|
|
86
|
+
// ❌ INP DAMAGE: Complex computation on mousemove (fires 60+/sec)
|
|
87
|
+
document.addEventListener('mousemove', (e) => {
|
|
88
|
+
renderComplexGradient(e.clientX, e.clientY);
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Section 3: CLS Audit Patterns
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
// ❌ CLS DAMAGE: Image without dimensions — shifts when loaded
|
|
98
|
+
<img src="/photo.jpg" /> // No width/height or aspect-ratio
|
|
99
|
+
|
|
100
|
+
// ❌ CLS DAMAGE: Dynamic content injected above fold
|
|
101
|
+
container.prepend(banner); // Pushes existing content down
|
|
102
|
+
|
|
103
|
+
// ✅ APPROVED: Reserved space with aspect-ratio
|
|
104
|
+
<div style={{ aspectRatio: '16/9', width: '100%' }}>
|
|
105
|
+
<Image src="/photo.jpg" fill alt="Photo" />
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
// ❌ CLS DAMAGE: Async font swap without size-adjust
|
|
109
|
+
// Fallback font metrics differ from web font → text reflows on swap
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Section 4: React 19 + Next.js 15 Patterns
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
// ❌ WATERFALL: Sequential use() calls creating fetch cascades
|
|
118
|
+
function Dashboard() {
|
|
119
|
+
const user = use(fetchUser()); // Waits for user
|
|
120
|
+
const posts = use(fetchPosts(user)); // THEN waits for posts — serial
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ✅ APPROVED: Parallel Suspense boundaries
|
|
124
|
+
function Dashboard() {
|
|
125
|
+
return (
|
|
126
|
+
<>
|
|
127
|
+
<Suspense fallback={<UserSkeleton />}><UserCard /></Suspense>
|
|
128
|
+
<Suspense fallback={<PostsSkeleton />}><PostsList /></Suspense>
|
|
129
|
+
</>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ❌ PAINT JANK: View Transition API started without checking support
|
|
134
|
+
document.startViewTransition(() => updateDOM());
|
|
135
|
+
|
|
136
|
+
// ✅ APPROVED: Feature-detect before using
|
|
137
|
+
if (document.startViewTransition) {
|
|
138
|
+
document.startViewTransition(() => updateDOM());
|
|
139
|
+
} else {
|
|
140
|
+
updateDOM();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ❌ SUSPENSE TOO HIGH: Single Suspense wrapping entire page
|
|
144
|
+
<Suspense fallback={<FullPageSpinner />}>
|
|
145
|
+
<Header /><Sidebar /><Content /><Footer />
|
|
146
|
+
</Suspense>
|
|
147
|
+
// All components wait for the slowest one — defeats Suspense purpose
|
|
148
|
+
|
|
149
|
+
// ✅ APPROVED: Granular Suspense per async boundary
|
|
150
|
+
<Header />
|
|
151
|
+
<Suspense fallback={<SidebarSkeleton />}><Sidebar /></Suspense>
|
|
152
|
+
<Suspense fallback={<ContentSkeleton />}><Content /></Suspense>
|
|
153
|
+
<Footer />
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Section 5: CSS Performance Opportunities
|
|
159
|
+
|
|
160
|
+
```css
|
|
161
|
+
/* ❌ MISSED OPTIMIZATION: Long scrollable list without content-visibility */
|
|
162
|
+
.feed-item {
|
|
163
|
+
/* Browser renders ALL items even off-screen */
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/* ✅ APPROVED: content-visibility skips rendering off-screen items */
|
|
167
|
+
.feed-item {
|
|
168
|
+
content-visibility: auto;
|
|
169
|
+
contain-intrinsic-size: auto 200px;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/* ❌ MISSED OPTIMIZATION: No CSS containment on isolated widgets */
|
|
173
|
+
.dashboard-card {
|
|
174
|
+
/* Recalculates layout for entire page when card content changes */
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/* ✅ APPROVED: contain: layout limits recalc scope */
|
|
178
|
+
.dashboard-card {
|
|
179
|
+
contain: layout;
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Section 6: Animation Frame Leaks
|
|
186
|
+
|
|
187
|
+
```tsx
|
|
188
|
+
// ❌ FRAME LEAK: useGSAP without cleanup (gsap timelines persist)
|
|
189
|
+
useEffect(() => {
|
|
190
|
+
gsap.to('.box', { x: 100 }); // No cleanup — leaks on unmount
|
|
191
|
+
}, []);
|
|
192
|
+
|
|
193
|
+
// ✅ APPROVED: useGSAP from @gsap/react handles cleanup automatically
|
|
194
|
+
import { useGSAP } from '@gsap/react';
|
|
195
|
+
useGSAP(() => {
|
|
196
|
+
gsap.to('.box', { x: 100 });
|
|
197
|
+
}, { scope: containerRef });
|
|
198
|
+
|
|
199
|
+
// ❌ FRAME LEAK: Framer Motion AnimatePresence without mode
|
|
200
|
+
<AnimatePresence>
|
|
201
|
+
{items.map(item => <motion.div key={item.id} />)}
|
|
202
|
+
</AnimatePresence>
|
|
203
|
+
// Exit + enter animations overlap — causes layout thrash
|
|
204
|
+
|
|
205
|
+
// ✅ APPROVED: mode="wait" prevents overlap
|
|
206
|
+
<AnimatePresence mode="wait">
|
|
207
|
+
{items.map(item => <motion.div key={item.id} exit={{ opacity: 0 }} />)}
|
|
208
|
+
</AnimatePresence>
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Verdict Format
|
|
214
|
+
|
|
215
|
+
```
|
|
216
|
+
[SEVERITY] vitals-reviewer | file.tsx:LINE
|
|
217
|
+
Metric: INP | LCP | CLS
|
|
218
|
+
Issue: [Specific pattern found]
|
|
219
|
+
Fix: [Exact code change]
|
|
220
|
+
Impact: [Estimated metric improvement]
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": 1,
|
|
3
|
+
"fingerprint": "54a75a8b",
|
|
4
|
+
"timestamp": "2026-04-12T00:58:14",
|
|
5
|
+
"domain": "security",
|
|
6
|
+
"verdict": "REJECTED",
|
|
7
|
+
"reason": "User input was concatenated directly into the system prompt, creating a critical prompt injection vulnerability.",
|
|
8
|
+
"pr_ref": "PR-1",
|
|
9
|
+
"reviewer": "security-auditor",
|
|
10
|
+
"tags": [
|
|
11
|
+
"systemprompt",
|
|
12
|
+
"you",
|
|
13
|
+
"helpful",
|
|
14
|
+
"assistant",
|
|
15
|
+
"context",
|
|
16
|
+
"userinput",
|
|
17
|
+
"response",
|
|
18
|
+
"generate",
|
|
19
|
+
"user",
|
|
20
|
+
"input",
|
|
21
|
+
"concatenated",
|
|
22
|
+
"directly",
|
|
23
|
+
"into",
|
|
24
|
+
"system",
|
|
25
|
+
"prompt",
|
|
26
|
+
"creating",
|
|
27
|
+
"critical",
|
|
28
|
+
"injection",
|
|
29
|
+
"vulnerability"
|
|
30
|
+
],
|
|
31
|
+
"diff_raw": "const systemPrompt = `You are a helpful assistant. Context: ${userInput}`;\nconst response = await ai.generate(systemPrompt);",
|
|
32
|
+
"diff_delta": "const systemPrompt = `You are a helpful assistant. Context: ${userInput}`;\nconst response = await ai.generate(systemPrompt);"
|
|
33
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0",
|
|
3
|
+
"cases": [
|
|
4
|
+
{
|
|
5
|
+
"id": 1,
|
|
6
|
+
"fingerprint": "54a75a8b",
|
|
7
|
+
"domain": "security",
|
|
8
|
+
"verdict": "REJECTED",
|
|
9
|
+
"tags": [
|
|
10
|
+
"systemprompt",
|
|
11
|
+
"you",
|
|
12
|
+
"helpful",
|
|
13
|
+
"assistant",
|
|
14
|
+
"context",
|
|
15
|
+
"userinput",
|
|
16
|
+
"response",
|
|
17
|
+
"generate",
|
|
18
|
+
"user",
|
|
19
|
+
"input",
|
|
20
|
+
"concatenated",
|
|
21
|
+
"directly",
|
|
22
|
+
"into",
|
|
23
|
+
"system",
|
|
24
|
+
"prompt",
|
|
25
|
+
"creating",
|
|
26
|
+
"critical",
|
|
27
|
+
"injection",
|
|
28
|
+
"vulnerability"
|
|
29
|
+
],
|
|
30
|
+
"timestamp": "2026-04-12T00:58:14",
|
|
31
|
+
"reason_summary": "User input was concatenated directly into the system prompt, creating a critical prompt injection vulnerability."
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"next_id": 2
|
|
35
|
+
}
|
package/.agent/rules/GEMINI.md
CHANGED
|
@@ -33,6 +33,7 @@ Every code or design request activates an agent. This is not optional.
|
|
|
33
33
|
|Domain|Primary Agent / Skill|
|
|
34
34
|
|---|---|
|
|
35
35
|
|API / server / backend|`backend-specialist`|
|
|
36
|
+
|API contract design / REST / GraphQL|`api-architect`|
|
|
36
37
|
|C# / .NET / Blazor|`dotnet-core-expert`|
|
|
37
38
|
|Python / FastAPI / Django|`python-pro`|
|
|
38
39
|
|Database / schema / SQL|`database-architect`|
|
|
@@ -43,6 +44,8 @@ Every code or design request activates an agent. This is not optional.
|
|
|
43
44
|
|Mobile (RN / Flutter)|`mobile-developer`|
|
|
44
45
|
|Debugging / errors|`debugger`|
|
|
45
46
|
|Security / vulnerabilities|`security-auditor`|
|
|
47
|
+
|Fault tolerance / retries / error boundaries|`resilience-reviewer`|
|
|
48
|
+
|Input validation / Zod / Pydantic schemas|`schema-reviewer`|
|
|
46
49
|
|Performance / optimization|`performance-optimizer`|
|
|
47
50
|
|DevOps / CI-CD / Docker|`devops-engineer`|
|
|
48
51
|
|Production incidents|`devops-incident-responder`|
|
|
@@ -50,6 +53,20 @@ Every code or design request activates an agent. This is not optional.
|
|
|
50
53
|
|Multi-agent architecture|`agent-organizer`|
|
|
51
54
|
|Multi-domain (2+ areas)|`orchestrator`|
|
|
52
55
|
|Unknown codebase|`explorer-agent`|
|
|
56
|
+
|Legacy code / codebase archaeology|`code-archaeologist`|
|
|
57
|
+
|Game development / Unity / Godot|`game-developer`|
|
|
58
|
+
|Documentation / README / API docs|`documentation-writer`|
|
|
59
|
+
|Test generation / test strategy|`test-engineer`|
|
|
60
|
+
|QA automation / E2E testing|`qa-automation-engineer`|
|
|
61
|
+
|Project planning / roadmaps|`project-planner`|
|
|
62
|
+
|Product strategy / feature prioritization|`product-manager`|
|
|
63
|
+
|User stories / backlog management|`product-owner`|
|
|
64
|
+
|SEO / search optimization|`seo-specialist`|
|
|
65
|
+
|Throughput / latency / load optimization|`throughput-optimizer`|
|
|
66
|
+
|Core Web Vitals / LCP / CLS / INP|`vitals-reviewer`|
|
|
67
|
+
|Pen testing / red team / attack surface|`penetration-tester`|
|
|
68
|
+
|Database performance / slow queries|`db-latency-auditor`|
|
|
69
|
+
|AI/LLM integration code / prompts|`ai-code-reviewer`|
|
|
53
70
|
|
|
54
71
|
**When activated, announce the agent:**
|
|
55
72
|
|
|
@@ -138,12 +155,12 @@ The Human Gate is never skipped. No code is written to a file without explicit u
|
|
|
138
155
|
|
|
139
156
|
|Code type|Reviewers|
|
|
140
157
|
|---|---|
|
|
141
|
-
|Backend/API|logic + security + dependency + type-safety|
|
|
158
|
+
|Backend/API|logic + security + dependency + type-safety + resilience + schema|
|
|
142
159
|
|Frontend/React|logic + security + frontend + type-safety|
|
|
143
|
-
|Database/SQL|logic + security + sql|
|
|
160
|
+
|Database/SQL|logic + security + sql + schema|
|
|
144
161
|
|Mobile/Cross-platform|logic + security + mobile-reviewer + type-safety|
|
|
145
162
|
|Any domain|+ performance (if optimization)|
|
|
146
|
-
|Before merge|/tribunal-full (all
|
|
163
|
+
|Before merge|/tribunal-full (all 16)|
|
|
147
164
|
|
|
148
165
|
---
|
|
149
166
|
|
|
@@ -205,7 +222,7 @@ These scripts live in `.agent/scripts/`. Agents and skills can invoke them:
|
|
|
205
222
|
|`checklist.py`|Priority audit: Security→Lint→Schema→Tests→UX→SEO|Before/after any major change|
|
|
206
223
|
|`verify_all.py`|Full pre-deploy validation suite|Pre-deploy|
|
|
207
224
|
|`auto_preview.py`|Start/stop/restart local dev server|After /create or /enhance|
|
|
208
|
-
|`session_manager.
|
|
225
|
+
|`session_manager.js`|Track session state between conversations|Multi-session work|
|
|
209
226
|
|`lint_runner.py`|Standalone lint runner (ESLint, Prettier, Ruff)|Every code change|
|
|
210
227
|
|`test_runner.py`|Standalone test runner (Jest, Vitest, pytest, Go)|After logic changes|
|
|
211
228
|
|`security_scan.py`|Deep OWASP-aware source code security scan|Always on deploy, /audit|
|
|
@@ -213,22 +230,22 @@ These scripts live in `.agent/scripts/`. Agents and skills can invoke them:
|
|
|
213
230
|
|`schema_validator.py`|Database schema validation (Prisma, SQL)|After DB changes|
|
|
214
231
|
|`bundle_analyzer.py`|JS/TS bundle size analysis|Before deploy|
|
|
215
232
|
|`skill_integrator.py`|Maps active skills to their executable scripts|Automatically when skills are invoked|
|
|
216
|
-
|`swarm_dispatcher.
|
|
217
|
-
|`test_swarm_dispatcher.
|
|
233
|
+
|`swarm_dispatcher.js`|Validate Orchestrator micro-worker JSON payloads|After /orchestrate, before dispatching agents|
|
|
234
|
+
|`test_swarm_dispatcher.js`|Unit tests for swarm_dispatcher|After modifying swarm_dispatcher.js|
|
|
218
235
|
|
|
219
236
|
**Run pattern:**
|
|
220
237
|
```
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
238
|
+
node .agent/scripts/checklist.js .
|
|
239
|
+
node .agent/scripts/verify_all.js
|
|
240
|
+
node .agent/scripts/security_scan.js .
|
|
224
241
|
python .agent/scripts/lint_runner.py . --fix
|
|
225
242
|
python .agent/scripts/test_runner.py . --coverage
|
|
226
243
|
python .agent/scripts/dependency_analyzer.py . --audit
|
|
227
244
|
python .agent/scripts/schema_validator.py .
|
|
228
245
|
python .agent/scripts/bundle_analyzer.py . --build
|
|
229
246
|
python .agent/scripts/skill_integrator.py
|
|
230
|
-
|
|
231
|
-
|
|
247
|
+
node .agent/scripts/swarm_dispatcher.js --file payload.json
|
|
248
|
+
npx jest test/integration/swarm_dispatcher.test.js
|
|
232
249
|
```
|
|
233
250
|
|
|
234
251
|
---
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared ANSI color constants for Tribunal Kit scripts.
|
|
3
|
+
* Import this module instead of duplicating color codes.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const GREEN = '\x1b[92m';
|
|
9
|
+
const YELLOW = '\x1b[93m';
|
|
10
|
+
const CYAN = '\x1b[96m';
|
|
11
|
+
const RED = '\x1b[91m';
|
|
12
|
+
const BLUE = '\x1b[94m';
|
|
13
|
+
const MAGENTA = '\x1b[95m';
|
|
14
|
+
const BOLD = '\x1b[1m';
|
|
15
|
+
const DIM = '\x1b[2m';
|
|
16
|
+
const RESET = '\x1b[0m';
|
|
17
|
+
|
|
18
|
+
module.exports = { GREEN, YELLOW, CYAN, RED, BLUE, MAGENTA, BOLD, DIM, RESET };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for Tribunal Kit scripts.
|
|
3
|
+
* Import this module instead of duplicating helper functions.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const { RED, RESET } = require('./_colors');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Walk up the directory tree to find the nearest .agent/ folder.
|
|
14
|
+
* @param {string} [startDir] - Directory to start searching from (defaults to cwd).
|
|
15
|
+
* @returns {string} Absolute path to the .agent directory.
|
|
16
|
+
*/
|
|
17
|
+
function findAgentDir(startDir) {
|
|
18
|
+
let current = path.resolve(startDir || process.cwd());
|
|
19
|
+
const root = path.parse(current).root;
|
|
20
|
+
|
|
21
|
+
while (current !== root) {
|
|
22
|
+
const candidate = path.join(current, '.agent');
|
|
23
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {
|
|
24
|
+
return candidate;
|
|
25
|
+
}
|
|
26
|
+
current = path.dirname(current);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.error(`${RED}✖ Error: '.agent' directory not found. Please run 'npx tribunal-kit init' first.${RESET}`);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Check if a package.json exists in the given directory.
|
|
35
|
+
* @param {string} dir - Directory to check.
|
|
36
|
+
* @returns {boolean}
|
|
37
|
+
*/
|
|
38
|
+
function hasNpm(dir) {
|
|
39
|
+
return fs.existsSync(path.join(dir, 'package.json'));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = { findAgentDir, hasNpm };
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* auto_preview.js — Start, stop, or check a local development server.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* node .agent/scripts/auto_preview.js start
|
|
7
|
+
* node .agent/scripts/auto_preview.js stop
|
|
8
|
+
* node .agent/scripts/auto_preview.js status
|
|
9
|
+
* node .agent/scripts/auto_preview.js restart
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const { spawn, execSync } = require('child_process');
|
|
17
|
+
const net = require('net');
|
|
18
|
+
|
|
19
|
+
const PID_FILE = ".preview.pid";
|
|
20
|
+
const DEFAULT_PORT = 3000;
|
|
21
|
+
const TIMEOUT_SECONDS = 30;
|
|
22
|
+
|
|
23
|
+
const { GREEN, RED, YELLOW, BOLD, RESET } = require('./colors.js');
|
|
24
|
+
|
|
25
|
+
function findStartCommand() {
|
|
26
|
+
const pkgPath = path.resolve("package.json");
|
|
27
|
+
if (!fs.existsSync(pkgPath)) return { cmd: [], found: false };
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
31
|
+
const scripts = pkg.scripts || {};
|
|
32
|
+
if (scripts.dev) return { cmd: ["npm", "run", "dev"], found: true };
|
|
33
|
+
if (scripts.start) return { cmd: ["npm", "run", "start"], found: true };
|
|
34
|
+
} catch {
|
|
35
|
+
// Ignore
|
|
36
|
+
}
|
|
37
|
+
return { cmd: [], found: false };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getPortFromEnv() {
|
|
41
|
+
const envPath = path.resolve(".env");
|
|
42
|
+
if (fs.existsSync(envPath)) {
|
|
43
|
+
try {
|
|
44
|
+
const data = fs.readFileSync(envPath, 'utf8');
|
|
45
|
+
for (const line of data.split('\n')) {
|
|
46
|
+
if (line.trim().startsWith("PORT=")) {
|
|
47
|
+
return parseInt(line.split("=")[1].trim(), 10);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
} catch {}
|
|
51
|
+
}
|
|
52
|
+
return DEFAULT_PORT;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function isPortOpen(port) {
|
|
56
|
+
return new Promise(resolve => {
|
|
57
|
+
const client = new net.Socket();
|
|
58
|
+
client.setTimeout(1000);
|
|
59
|
+
client.once('connect', () => {
|
|
60
|
+
client.destroy();
|
|
61
|
+
resolve(true);
|
|
62
|
+
}).once('timeout', () => {
|
|
63
|
+
client.destroy();
|
|
64
|
+
resolve(false);
|
|
65
|
+
}).once('error', () => {
|
|
66
|
+
resolve(false);
|
|
67
|
+
}).connect(port, 'localhost');
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function readPid() {
|
|
72
|
+
if (fs.existsSync(PID_FILE)) {
|
|
73
|
+
try {
|
|
74
|
+
return parseInt(fs.readFileSync(PID_FILE, 'utf8').trim(), 10);
|
|
75
|
+
} catch {}
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function writePid(pid) {
|
|
81
|
+
fs.writeFileSync(PID_FILE, String(pid), 'utf8');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function clearPid() {
|
|
85
|
+
if (fs.existsSync(PID_FILE)) {
|
|
86
|
+
fs.unlinkSync(PID_FILE);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function startServer() {
|
|
91
|
+
const port = getPortFromEnv();
|
|
92
|
+
|
|
93
|
+
if (await isPortOpen(port)) {
|
|
94
|
+
console.log(`${YELLOW}⚠️ Port ${port} is already in use.${RESET}`);
|
|
95
|
+
const pid = readPid();
|
|
96
|
+
if (pid) console.log(` Known PID: ${pid}`);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const { cmd, found } = findStartCommand();
|
|
101
|
+
if (!found) {
|
|
102
|
+
console.log(`${RED}❌ No dev/start script found.${RESET}`);
|
|
103
|
+
console.log(` This project has no package.json, or its package.json has no 'dev' or 'start' script.`);
|
|
104
|
+
console.log(` Add a script to package.json, or start your server manually.`);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
console.log(`${BOLD}Starting: ${cmd.join(' ')}${RESET}`);
|
|
109
|
+
// Adjust command for windows (npm.cmd instead of npm)
|
|
110
|
+
const executable = process.platform === 'win32' ? `${cmd[0]}.cmd` : cmd[0];
|
|
111
|
+
|
|
112
|
+
const proc = spawn(executable, cmd.slice(1), {
|
|
113
|
+
stdio: 'pipe',
|
|
114
|
+
detached: true
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Ignore children stdout inside detached mode to let node exit
|
|
118
|
+
proc.stdout.unref();
|
|
119
|
+
proc.stderr.unref();
|
|
120
|
+
proc.unref();
|
|
121
|
+
|
|
122
|
+
writePid(proc.pid);
|
|
123
|
+
|
|
124
|
+
process.stdout.write(`Waiting for port ${port}…`);
|
|
125
|
+
for (let i = 0; i < TIMEOUT_SECONDS; i++) {
|
|
126
|
+
if (await isPortOpen(port)) {
|
|
127
|
+
console.log(`\n${GREEN}✅ Server started${RESET}`);
|
|
128
|
+
console.log(` URL: http://localhost:${port}`);
|
|
129
|
+
console.log(` PID: ${proc.pid}`);
|
|
130
|
+
console.log(` Command: ${cmd.join(' ')}`);
|
|
131
|
+
console.log(`\nStop with: node .agent/scripts/auto_preview.js stop`);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
process.stdout.write(".");
|
|
135
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
console.log(`\n${RED}❌ Server did not start within ${TIMEOUT_SECONDS}s${RESET}`);
|
|
139
|
+
try {
|
|
140
|
+
process.kill(proc.pid, 'SIGTERM');
|
|
141
|
+
} catch {}
|
|
142
|
+
clearPid();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function stopServer() {
|
|
146
|
+
const pid = readPid();
|
|
147
|
+
if (!pid) {
|
|
148
|
+
console.log(`${YELLOW}⚠️ No stored server PID found${RESET}`);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
process.kill(pid, 'SIGTERM');
|
|
153
|
+
console.log(`${GREEN}✅ Server stopped (PID ${pid})${RESET}`);
|
|
154
|
+
} catch {
|
|
155
|
+
console.log(`${YELLOW}Process ${pid} was not running${RESET}`);
|
|
156
|
+
} finally {
|
|
157
|
+
clearPid();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function showStatus() {
|
|
162
|
+
const port = getPortFromEnv();
|
|
163
|
+
const pid = readPid();
|
|
164
|
+
if (await isPortOpen(port)) {
|
|
165
|
+
console.log(`${GREEN}🟢 Running — http://localhost:${port}${RESET}`);
|
|
166
|
+
if (pid) console.log(` PID: ${pid}`);
|
|
167
|
+
} else {
|
|
168
|
+
console.log(`${RED}🔴 Not running on port ${port}${RESET}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function main() {
|
|
173
|
+
const args = process.argv.slice(2);
|
|
174
|
+
const actions = new Set(["start", "stop", "status", "restart"]);
|
|
175
|
+
|
|
176
|
+
if (args.length < 1 || !actions.has(args[0])) {
|
|
177
|
+
console.log(`Usage: node auto_preview.js [start|stop|status|restart]`);
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const action = args[0];
|
|
182
|
+
if (action === "start") {
|
|
183
|
+
await startServer();
|
|
184
|
+
} else if (action === "stop") {
|
|
185
|
+
stopServer();
|
|
186
|
+
} else if (action === "status") {
|
|
187
|
+
await showStatus();
|
|
188
|
+
} else if (action === "restart") {
|
|
189
|
+
stopServer();
|
|
190
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
191
|
+
await startServer();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (require.main === module) {
|
|
196
|
+
main();
|
|
197
|
+
}
|