tribunal-kit 4.3.1 → 4.4.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 (67) hide show
  1. package/.agent/agents/api-architect.md +66 -66
  2. package/.agent/agents/db-latency-auditor.md +216 -216
  3. package/.agent/agents/precedence-reviewer.md +250 -250
  4. package/.agent/agents/resilience-reviewer.md +88 -88
  5. package/.agent/agents/schema-reviewer.md +67 -67
  6. package/.agent/agents/throughput-optimizer.md +299 -299
  7. package/.agent/agents/ui-ux-auditor.md +292 -292
  8. package/.agent/agents/vitals-reviewer.md +223 -223
  9. package/.agent/scripts/_colors.js +18 -18
  10. package/.agent/scripts/_utils.js +42 -42
  11. package/.agent/scripts/append_flow.js +72 -72
  12. package/.agent/scripts/auto_preview.js +197 -197
  13. package/.agent/scripts/bundle_analyzer.js +290 -290
  14. package/.agent/scripts/case_law_manager.js +17 -6
  15. package/.agent/scripts/checklist.js +266 -266
  16. package/.agent/scripts/colors.js +17 -17
  17. package/.agent/scripts/compress_skills.js +141 -141
  18. package/.agent/scripts/consolidate_skills.js +149 -149
  19. package/.agent/scripts/context_broker.js +611 -609
  20. package/.agent/scripts/deep_compress.js +150 -150
  21. package/.agent/scripts/dependency_analyzer.js +272 -272
  22. package/.agent/scripts/graph_builder.js +151 -37
  23. package/.agent/scripts/graph_visualizer.js +384 -0
  24. package/.agent/scripts/inner_loop_validator.js +451 -465
  25. package/.agent/scripts/lint_runner.js +187 -187
  26. package/.agent/scripts/minify_context.js +100 -100
  27. package/.agent/scripts/mutation_runner.js +280 -0
  28. package/.agent/scripts/patch_skills_meta.js +156 -156
  29. package/.agent/scripts/patch_skills_output.js +244 -244
  30. package/.agent/scripts/schema_validator.js +297 -297
  31. package/.agent/scripts/security_scan.js +303 -303
  32. package/.agent/scripts/session_manager.js +276 -276
  33. package/.agent/scripts/skill_evolution.js +644 -644
  34. package/.agent/scripts/skill_integrator.js +313 -313
  35. package/.agent/scripts/strengthen_skills.js +193 -193
  36. package/.agent/scripts/strip_tribunal.js +47 -47
  37. package/.agent/scripts/swarm_dispatcher.js +360 -360
  38. package/.agent/scripts/test_runner.js +193 -193
  39. package/.agent/scripts/utils.js +32 -32
  40. package/.agent/scripts/verify_all.js +257 -256
  41. package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +1 -1
  42. package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +1 -1
  43. package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +1 -1
  44. package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +1 -1
  45. package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +1 -1
  46. package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +1 -1
  47. package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +1 -1
  48. package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +1 -1
  49. package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +1 -1
  50. package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +1 -1
  51. package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +1 -1
  52. package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +1 -1
  53. package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +1 -1
  54. package/.agent/skills/doc.md +1 -1
  55. package/.agent/skills/knowledge-graph/SKILL.md +32 -16
  56. package/.agent/skills/testing-patterns/SKILL.md +19 -2
  57. package/.agent/skills/ui-ux-pro-max/SKILL.md +480 -43
  58. package/.agent/workflows/generate.md +183 -183
  59. package/.agent/workflows/tribunal-speed.md +183 -183
  60. package/README.md +1 -1
  61. package/bin/tribunal-kit.js +134 -17
  62. package/package.json +6 -3
  63. package/scripts/changelog.js +167 -167
  64. package/scripts/sync-version.js +81 -81
  65. package/.agent/scripts/__pycache__/_colors.cpython-311.pyc +0 -0
  66. package/.agent/scripts/__pycache__/_utils.cpython-311.pyc +0 -0
  67. package/.agent/scripts/__pycache__/case_law_manager.cpython-311.pyc +0 -0
@@ -1,223 +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
- ---
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
+ ---
@@ -1,18 +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 };
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 };
@@ -1,42 +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 };
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 };